Parcourir la source

Minor changes & add basic settings

jerryliao il y a 1 an
Parent
commit
1b7b359943
11 fichiers modifiés avec 305 ajouts et 106 suppressions
  1. 0 35
      deno.lock
  2. 22 20
      fresh.gen.ts
  3. 5 9
      islands/Editor.tsx
  4. 60 6
      islands/Modal.tsx
  5. 1 1
      islands/PostList.tsx
  6. 119 20
      islands/TopBar.tsx
  7. 9 5
      routes/[id].tsx
  8. 9 5
      routes/api/post.tsx
  9. 31 0
      routes/api/share.tsx
  10. 1 1
      routes/index.tsx
  11. 48 4
      static/global.css

+ 0 - 35
deno.lock

@@ -125,8 +125,6 @@
     "https://deno.land/x/importmap@0.2.1/mod.ts": "ae3d1cd7eabd18c01a4960d57db471126b020f23b37ef14e1359bbb949227ade",
     "https://deno.land/x/numesis@1.1.0/mod.ts": "c0f8ceffece2b417e9cdfe8cc50408a36b35a4b5ca260451a85b2169df5ec3fd",
     "https://deno.land/x/rutt@0.1.0/mod.ts": "4662ad4f687740ac612b779ed4c62eecebd718b56d24a07f719ec3b24464c139",
-    "https://deno.land/x/short_uuid@v3.0.0-rc1/mod.ts": "cfafcd34f057543f4e47fedaa3c8739b30dc712909ebff6a3a9a11fb4468a790",
-    "https://deno.land/x/short_uuid@v3.0.0-rc1/version.json": "a14637ca453edc6b848ebc3282fa16419efad918aba9c386a7bb6885dd3ba9eb",
     "https://deno.land/x/sqlite@v3.7.1/build/sqlite.d.ts": "d724a21a588a0e19ae46a3476349fe5d75e896735e7362ea8cf997ffad35d0f9",
     "https://deno.land/x/sqlite@v3.7.1/build/sqlite.js": "c59f109f100c2bae0b9342f04e0d400583e2e3211d08bb71095177a4109ee5bf",
     "https://deno.land/x/sqlite@v3.7.1/build/vfs.js": "08533cc78fb29b9d9bd62f6bb93e5ef333407013fed185776808f11223ba0e70",
@@ -147,46 +145,13 @@
     "https://deno.land/x/ts_morph@17.0.1/ts_morph.d.ts": "a54b0c51b06d84defedf5fdd59c773d803808ae7c9678f7165f7a1a6dfa7f6a3",
     "https://deno.land/x/ts_morph@17.0.1/ts_morph.js": "1bb80284b9e31a4c5c2078cd533fe9b12b4b2d710267055cb655225aa88fb2df",
     "https://deno.land/x/usid@2.0.0/mod.ts": "cf03ac641ae7ad533d900eaedf8bd62a9dbb6ddbc1e50db1c2f4f143fcb608ab",
-    "https://esm.sh/lodash@4.17.21/debounce": "6e1de1c30f2e685be1082e4904c7ed95c10829b2e30c3a3e35fdb714ef816736",
-    "https://esm.sh/lodash@4.17.21/throttle": "996b17e983a8a862c5d1761d94b417436ae3cef82f7d7cedc54ae04aa929e2e9",
     "https://esm.sh/preact-render-to-string@6.0.2?deps=preact@10.13.2": "df36fa1d653fc00225ec484bee9d8075df042f21cb861b97d40b5aff9029a6c7",
     "https://esm.sh/preact@10.13.2": "5ebf0838bbc3c32fc6e78a4cd9dd672f2c36386d0595815d721a6b0941278488",
     "https://esm.sh/preact@10.13.2/hooks": "884334b1560448cf16b4f14841fffdb8707615373a3c76c676a6f9e5c77e43b2",
     "https://esm.sh/showdown@2.1.0": "8401aeb424b74dcdf53811256d1467343610fc05c0430bb2460d4a3c4d5d5e34",
     "https://esm.sh/stable/preact@10.13.2/deno/hooks.js": "c7a8e703bcbc6a05949f329b618c33d5d1ea5fee113ddcea44ff0f527af8556f",
     "https://esm.sh/stable/preact@10.13.2/deno/preact.mjs": "365fab897381f4f403f859c5d12939084560545567108cc90dae901bbe892578",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/array.d.ts": "458111fc89d11d2151277c822dfdc1a28fa5b6b2493cf942e37d4cd0a6ee5f22",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/collection.d.ts": "d70c026dd2eeaa974f430ea229230a1897fdb897dc74659deebe2afd4feeb08f",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/common.d.ts": "675e702f2032766a91eeadee64f51014c64688525da99dccd8178f0c599f13a8",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/date.d.ts": "187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/function.d.ts": "febf0b2de54781102b00f61653b21377390a048fbf5262718c91860d11ff34a6",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/lang.d.ts": "98f9d826db9cd99d27a01a59ee5f22863df00ccf1aaf43e1d7db80ebf716f7c3",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/math.d.ts": "0aaef8cded245bf5036a7a40b65622dd6c4da71f7a35343112edbe112b348a1e",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/number.d.ts": "00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/object.d.ts": "dcd91d3b697cb650b95db5471189b99815af5db2a1cd28760f91e0b12ede8ed5",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/seq.d.ts": "3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/string.d.ts": "3cf0d343c2276842a5b617f22ba82af6322c7cfe8bb52238ffc0c491a3c21019",
-    "https://esm.sh/v116/@types/lodash@4.14.194/common/util.d.ts": "df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9",
-    "https://esm.sh/v116/@types/lodash@4.14.194/debounce~.d.ts": "9905281a4c9cf23ee1a9d057d1f39001be920891aa1f70af1299c5d87ca415ed",
-    "https://esm.sh/v116/@types/lodash@4.14.194/index.d.ts": "f2eff8704452659641164876c1ef0df4174659ce7311b0665798ea3f556fa9ad",
-    "https://esm.sh/v116/@types/lodash@4.14.194/index~.d.ts": "f2eff8704452659641164876c1ef0df4174659ce7311b0665798ea3f556fa9ad",
-    "https://esm.sh/v116/@types/lodash@4.14.194/throttle~.d.ts": "45124b18f9b81a45134db1f87474642d73b2d107666dce00b6837a58dc2e48d4",
     "https://esm.sh/v116/@types/showdown@2.0.0/index.d.ts": "0cae38d42cef880ce4848fcd9ed704d65ba3ecc825443cd548ff81931c64cdca",
-    "https://esm.sh/v116/lodash@4.17.21/deno/_Symbol.js": "1b59f6fb5e8a0581931ac79bc340db7e60141a3ed1ae57071489e4e050c3cd3c",
-    "https://esm.sh/v116/lodash@4.17.21/deno/_baseGetTag.js": "e3153929bcf9bb84bcbf6e64c444f2dec4f184f4978aca556447c8cc853f4d5d",
-    "https://esm.sh/v116/lodash@4.17.21/deno/_baseTrim.js": "5d2d3bd7093a9d683faa672a85ee2162e0dcacdb3292ff6f743d0d565954c866",
-    "https://esm.sh/v116/lodash@4.17.21/deno/_freeGlobal.js": "447608867b27f11ab7f56689d66ae4039cad253a2a23cb938d9fcc9d76ae851a",
-    "https://esm.sh/v116/lodash@4.17.21/deno/_getRawTag.js": "619a5eabaa02d9b6a0a1bf21479af52b7fccf79205df6691b4f563671b8a3916",
-    "https://esm.sh/v116/lodash@4.17.21/deno/_objectToString.js": "24ff88d51d498df9bc088cfdb04d0c4c7c2994222d76cd8fb0ab44351850fa2b",
-    "https://esm.sh/v116/lodash@4.17.21/deno/_root.js": "b9261ef2793dfc9b554f7792bce207b9ac96b51ebfa40c396554438a054f2cdb",
-    "https://esm.sh/v116/lodash@4.17.21/deno/_trimmedEndIndex.js": "6c1f7b5703754aa023a9073626e289f53997964f6b0b45a3e189526b4f0fa93a",
-    "https://esm.sh/v116/lodash@4.17.21/deno/debounce.js": "9b61d8f726b2edef89bd02661301de2497248e2e231c1d8700be1b5d7549aea6",
-    "https://esm.sh/v116/lodash@4.17.21/deno/isObject.js": "eb62b3ca7b47f1fa4e019c86abdb5583b6668c6f7f975f8b756f458eb4dfdea9",
-    "https://esm.sh/v116/lodash@4.17.21/deno/isObjectLike.js": "04ce534c0b87fa884d7f8c6dd0ac98f2f278c54a34ace14c399e00f64c902358",
-    "https://esm.sh/v116/lodash@4.17.21/deno/isSymbol.js": "7aa09c0401bc53361a2c1568192e5437088a850adca8437dfbd62981b4b61f2d",
-    "https://esm.sh/v116/lodash@4.17.21/deno/now.js": "09c5cfb11430a8c2a0abe287dccbbddf57a3f383b6f41dc6f909965d26a45730",
-    "https://esm.sh/v116/lodash@4.17.21/deno/throttle.js": "51b437e666781ec14642e4499532ef28c39e2f18422dcd66027b683b6ded0b5c",
-    "https://esm.sh/v116/lodash@4.17.21/deno/toNumber.js": "7ba1d7b56501ddd6fabb52c8411189a81762e3dc08e9c726ea6e363b6f969ed5",
     "https://esm.sh/v116/preact-render-to-string@6.0.2/X-ZC9wcmVhY3RAMTAuMTMuMg/deno/preact-render-to-string.mjs": "09ead691b3745189a4171a6ee0948592c7a862b1bf1a97fe651fb34f88eabe3b",
     "https://esm.sh/v116/preact-render-to-string@6.0.2/X-ZC9wcmVhY3RAMTAuMTMuMg/src/index.d.ts": "36e470f81eacf9700b80c92d7bf7342f0d46e2e7152a31cbe166576bb634a96e",
     "https://esm.sh/v116/preact@10.13.2/hooks/src/index.d.ts": "5c29febb624fc25d71cb0e125848c9b711e233337a08f7eacfade38fd4c14cc3",

+ 22 - 20
fresh.gen.ts

@@ -3,16 +3,17 @@
 // This file is automatically updated during development when running `dev.ts`.
 
 import config from "./deno.json" assert { type: "json" };
-import * as $0 from "./routes/_404.tsx";
-import * as $1 from "./routes/_app.tsx";
-import * as $2 from "./routes/api/post.tsx";
-import * as $3 from "./routes/api/user/login.tsx";
-import * as $4 from "./routes/api/user/logout.tsx";
-import * as $5 from "./routes/api/user/register.tsx";
-import * as $6 from "./routes/index.tsx";
-import * as $7 from "./routes/login.tsx";
-import * as $8 from "./routes/post/[id].tsx";
-import * as $9 from "./routes/register.tsx";
+import * as $0 from "./routes/[id].tsx";
+import * as $1 from "./routes/_404.tsx";
+import * as $2 from "./routes/_app.tsx";
+import * as $3 from "./routes/api/post.tsx";
+import * as $4 from "./routes/api/share.tsx";
+import * as $5 from "./routes/api/user/login.tsx";
+import * as $6 from "./routes/api/user/logout.tsx";
+import * as $7 from "./routes/api/user/register.tsx";
+import * as $8 from "./routes/index.tsx";
+import * as $9 from "./routes/login.tsx";
+import * as $10 from "./routes/register.tsx";
 import * as $$0 from "./islands/Editor.tsx";
 import * as $$1 from "./islands/HomeBar.tsx";
 import * as $$2 from "./islands/LoginFrame.tsx";
@@ -22,16 +23,17 @@ import * as $$5 from "./islands/TopBar.tsx";
 
 const manifest = {
   routes: {
-    "./routes/_404.tsx": $0,
-    "./routes/_app.tsx": $1,
-    "./routes/api/post.tsx": $2,
-    "./routes/api/user/login.tsx": $3,
-    "./routes/api/user/logout.tsx": $4,
-    "./routes/api/user/register.tsx": $5,
-    "./routes/index.tsx": $6,
-    "./routes/login.tsx": $7,
-    "./routes/post/[id].tsx": $8,
-    "./routes/register.tsx": $9,
+    "./routes/[id].tsx": $0,
+    "./routes/_404.tsx": $1,
+    "./routes/_app.tsx": $2,
+    "./routes/api/post.tsx": $3,
+    "./routes/api/share.tsx": $4,
+    "./routes/api/user/login.tsx": $5,
+    "./routes/api/user/logout.tsx": $6,
+    "./routes/api/user/register.tsx": $7,
+    "./routes/index.tsx": $8,
+    "./routes/login.tsx": $9,
+    "./routes/register.tsx": $10,
   },
   islands: {
     "./islands/Editor.tsx": $$0,

+ 5 - 9
islands/Editor.tsx

@@ -13,7 +13,6 @@ export enum EditorMode {
 
 interface EditorProps {
   id: string;
-  title: string;
   content: string;
   allowMode: EditorMode;
 }
@@ -64,7 +63,7 @@ export default function Editor(props: EditorProps) {
 
       // Sync scroll ratio
       const syncElement =
-        scrollSide === EditorMode.Edit
+        scrollSide === EditorMode.Read
           ? editViewRef.current &&
             (editViewRef.current as HTMLDivElement).querySelector("textarea")
           : readViewRef.current;
@@ -94,7 +93,6 @@ export default function Editor(props: EditorProps) {
       headers: { "Content-Type": "application/json" },
       body: JSON.stringify({
         id: props.id,
-        title: props.title,
         content,
       }),
     });
@@ -159,12 +157,10 @@ export default function Editor(props: EditorProps) {
 
   // Init conversion
   useEffect(() => {
-    if (props.title) {
-      setDisplayContent(props.content);
-      convertText(props.content);
-      hideLoading();
-    }
-  }, [props.title]);
+    setDisplayContent(props.content);
+    convertText(props.content);
+    hideLoading();
+  }, [props.content]);
 
   const convertText = (text: string) => {
     // Init converter

+ 60 - 6
islands/Modal.tsx

@@ -1,10 +1,20 @@
 /** @jsx h */
 /** @jsxFrag Fragment */
-import { Fragment, h } from "preact";
+import { JSX, Fragment, h } from "preact";
 import { useState, useEffect } from "preact/hooks";
 
+interface ModalAction {
+  text: string;
+  onClick?: (text: string) => void | Promise<void>;
+}
+
 interface ModalGlobalHook {
-  setVisible: (flag: boolean) => void;
+  show: (
+    title: string,
+    content: string | JSX.Element,
+    actions: ModalAction[]
+  ) => void;
+  hide: () => void;
 }
 
 declare global {
@@ -15,12 +25,33 @@ declare global {
 
 export default function Modal() {
   const [visible, setVisible] = useState(false);
+  const [title, setTitle] = useState("");
+  const [content, setContent] = useState<string | JSX.Element>("");
+  const [actions, setActions] = useState<ModalAction[]>([]);
+
+  const showModal = (
+    newTitle: string,
+    newContent: string | JSX.Element,
+    newActions: ModalAction[]
+  ) => {
+    setTitle(newTitle || "");
+    setContent(newContent || "");
+    setActions(newActions || []);
+    setVisible(true);
+  };
+
+  const hideModal = () => {
+    setVisible(false);
+  };
 
   useEffect(() => {
     window.$modal = {
-      setVisible: (flag: boolean) => {
-        setVisible(flag);
-      },
+      show: (
+        title: string,
+        content: string | JSX.Element,
+        actions: ModalAction[]
+      ) => showModal(title, content, actions),
+      hide: () => hideModal(),
     };
 
     return () => {
@@ -31,7 +62,30 @@ export default function Modal() {
   return (
     <>
       <div className={`pd-modal${!visible ? " pd-modal-hidden" : ""}`}>
-        <div className="pd-modal-content"></div>
+        <div className="pd-modal-content">
+          <i
+            className="bi bi-x pd-modal-close"
+            onClick={() => {
+              hideModal();
+            }}
+          />
+          {title ? <div className="pd-modal-title">{title}</div> : null}
+          <div className="pd-modal-body">{content}</div>
+          {actions.length > 0 ? (
+            <div className="pd-modal-footer">
+              {actions.map((action, index) => (
+                <button
+                  key={index}
+                  onClick={() => {
+                    action.onClick ? action.onClick(action.text) : hideModal();
+                  }}
+                >
+                  {action.text}
+                </button>
+              ))}
+            </div>
+          ) : null}
+        </div>
       </div>
     </>
   );

+ 1 - 1
islands/PostList.tsx

@@ -7,7 +7,7 @@ interface PostListProps {
 
 export default function PostList(props: PostListProps) {
   const onEdit = (id: string) => {
-    location.href = `post/${id}`;
+    location.href = `/${id}`;
   };
 
   return (

+ 119 - 20
islands/TopBar.tsx

@@ -6,11 +6,117 @@ import { EditorMode } from "./Editor.tsx";
 interface TopBarProps {
   allowMode: EditorMode;
   isLogined: boolean;
+  shared: boolean;
+  title: string;
+  id: string;
 }
 
+const settingsData: { [key: string]: string } = {};
+const shareData: { [key: string]: boolean } = {};
+
 export default function TopBar(props: TopBarProps) {
   const [mode, setMode] = useState(props.allowMode);
 
+  const doLogout = async () => {
+    const resp = await fetch("/api/user/logout");
+    const respJson = await resp.json();
+    if (respJson.success) {
+      location.href = "/login";
+      return true;
+    }
+    return false;
+  };
+
+  const goHome = () => {
+    location.href = "/";
+  };
+
+  const showShare = () => {
+    shareData["shared"] = shareData["submittedShared"] || props.shared;
+    window.$modal?.show(
+      "Share options",
+      <div style="display: flex; align-items: center">
+        <input
+          type="checkbox"
+          checked={shareData["shared"]}
+          onChange={(e) => {
+            shareData["shared"] = (e.target as HTMLInputElement).checked;
+            (
+              document.querySelector("#shared-button") as HTMLButtonElement
+            ).style.visibility = shareData["shared"] ? "visible" : "hidden";
+          }}
+        />
+        <span style="margin: 0 8px">Shared to friends</span>
+        <button
+          id="shared-button"
+          style={`${!shareData["shared"] ? ";visibility: hidden" : ""}`}
+          onClick={async () => {
+            await navigator.clipboard.writeText(location.href.split("?")[0]);
+          }}
+        >
+          Copy Link
+        </button>
+      </div>,
+      [
+        {
+          text: "Confirm",
+          onClick: async () => {
+            const resp = await fetch("/api/share", {
+              method: "POST",
+              headers: { "Content-Type": "application/json" },
+              body: JSON.stringify({
+                id: props.id,
+                shared: shareData["shared"],
+              }),
+            });
+            const respJson = await resp.json();
+            if (respJson.success) {
+              shareData["submittedShared"] = shareData["shared"];
+              window.$modal?.hide();
+            }
+          },
+        },
+      ]
+    );
+  };
+
+  const showSetting = () => {
+    settingsData["title"] = settingsData["submittedTitle"] || props.title;
+    window.$modal?.show(
+      "Post Settings",
+      <div style="display: flex; align-items: center">
+        <span style="margin-right: 8px">Title</span>
+        <input
+          placeholder="Post title here"
+          value={settingsData["title"]}
+          onInput={(e) => {
+            settingsData["title"] = (e.target as HTMLInputElement).value;
+          }}
+        />
+      </div>,
+      [
+        {
+          text: "Confirm",
+          onClick: async () => {
+            const resp = await fetch("/api/post", {
+              method: "PUT",
+              headers: { "Content-Type": "application/json" },
+              body: JSON.stringify({
+                id: props.id,
+                title: settingsData["title"],
+              }),
+            });
+            const respJson = await resp.json();
+            if (respJson.success) {
+              settingsData["submittedTitle"] = settingsData["title"];
+              window.$modal?.hide();
+            }
+          },
+        },
+      ]
+    );
+  };
+
   // Event listener
   const modeChangeListener = (e: CustomEvent) => {
     if (
@@ -26,20 +132,6 @@ export default function TopBar(props: TopBarProps) {
     dispatchEvent(new CustomEvent("ModeChange", { detail: mode }));
   };
 
-  const doLogout = async () => {
-    const resp = await fetch("/api/user/logout");
-    const respJson = await resp.json();
-    if (respJson.success) {
-      location.href = "/login";
-      return true;
-    }
-    return false;
-  };
-
-  const goHome = () => {
-    location.href = "/";
-  };
-
   // Init event listeners
   useEffect(() => {
     addEventListener("ModeChange", modeChangeListener);
@@ -51,11 +143,15 @@ export default function TopBar(props: TopBarProps) {
 
   return (
     <div className="pd-top-bar">
-      <div className="pd-top-bar-mode-switcher">
+      <div
+        className={`pd-top-bar-mode-switcher${
+          mode !== EditorMode.Both ? " hidden" : ""
+        }`}
+      >
         <button
           className={`pd-top-bar-btn${
             mode === EditorMode.Edit ? " active" : ""
-          }${props.allowMode === EditorMode.Read ? " disabled" : ""}`}
+          }`}
           id="edit"
           type="button"
           onClick={() => {
@@ -67,7 +163,7 @@ export default function TopBar(props: TopBarProps) {
         <button
           className={`pd-top-bar-btn${
             mode === EditorMode.Read ? " active" : ""
-          }${props.allowMode === EditorMode.Edit ? " disabled" : ""}`}
+          }`}
           id="read"
           type="button"
           onClick={() => {
@@ -79,7 +175,7 @@ export default function TopBar(props: TopBarProps) {
         <button
           className={`pd-top-bar-btn${
             mode === EditorMode.Both ? " active" : ""
-          }${props.allowMode !== EditorMode.Both ? " disabled" : ""}`}
+          }`}
           id="both"
           type="button"
           onClick={() => {
@@ -89,12 +185,15 @@ export default function TopBar(props: TopBarProps) {
           Both
         </button>
       </div>
+      {!props.isLogined ? (
+        <span className="pd-top-bar-title">{props.title}</span>
+      ) : null}
       {props.isLogined ? (
         <div className="pd-top-bar-tool-icons">
           <i className="bi bi-box-arrow-left" onClick={doLogout} />
           <i className="bi bi-house-door" onClick={goHome} />
-          <i className="bi bi-share" />
-          <i className="bi bi-gear" />
+          <i className="bi bi-share" onClick={showShare} />
+          <i className="bi bi-gear" onClick={showSetting} />
         </div>
       ) : null}
     </div>

+ 9 - 5
routes/post/[id].tsx → routes/[id].tsx

@@ -5,13 +5,14 @@ import { Head } from "$fresh/runtime.ts";
 import { Handlers, PageProps } from "$fresh/server.ts";
 import { checkToken } from "utils/server.ts";
 import { find } from "utils/db.ts";
-import TopBar from "../../islands/TopBar.tsx";
-import Editor, { EditorMode } from "../../islands/Editor.tsx";
+import TopBar from "../islands/TopBar.tsx";
+import Editor, { EditorMode } from "../islands/Editor.tsx";
 
 interface PostProps {
   id: string;
   title: string;
   content: string;
+  shared: boolean;
   isLogined: boolean;
   allowMode: EditorMode;
 }
@@ -28,15 +29,16 @@ export const handler: Handlers<PostProps> = {
             user_id: tokenUserId,
           }
         : { id: postId, shared: true },
-      ["title", "content"]
+      ["title", "content", "shared"]
     );
     if (post.length > 0) {
       return ctx.render({
         id: postId,
         isLogined: Boolean(tokenUserId),
         allowMode: tokenUserId ? EditorMode.Both : EditorMode.Read,
-        title: (post[0][0] as string) || "Untitled",
+        title: post[0][0] as string,
         content: post[0][1] as string,
+        shared: post[0][2] as boolean,
       });
     }
     // Redirect to 404 page if not found
@@ -52,12 +54,14 @@ export default function Post(props: PageProps) {
       </Head>
       <div className="pd-page">
         <TopBar
+          id={props.data.id}
+          title={props.data.title}
+          shared={props.data.shared}
           allowMode={props.data.allowMode}
           isLogined={props.data.isLogined}
         />
         <Editor
           id={props.data.id}
-          title={props.data.title}
           content={props.data.content}
           allowMode={props.data.allowMode}
         />

+ 9 - 5
routes/api/post.tsx

@@ -61,16 +61,20 @@ export const handler: Handlers = {
     const title = reqJson.title;
     const content = reqJson.content;
     const tokenUserId = checkToken(req);
-    if (tokenUserId && id && title && content) {
+    if (tokenUserId && id && (title || content)) {
       const post = find("Post", {
         id,
         user_id: tokenUserId,
       });
       if (post.length > 0) {
-        const newPost = update("Post", id, {
-          title,
-          content,
-        });
+        const updateObject: { [key: string]: string } = {};
+        if (title) {
+          updateObject["title"] = title;
+        }
+        if (content) {
+          updateObject["content"] = content;
+        }
+        const newPost = update("Post", id, updateObject);
         if (newPost.length > 0) {
           return makeSuccessResponse(true);
         }

+ 31 - 0
routes/api/share.tsx

@@ -0,0 +1,31 @@
+import { Handlers } from "$fresh/server.ts";
+import {
+  checkToken,
+  makeErrorResponse,
+  makeSuccessResponse,
+} from "utils/server.ts";
+import { find, update } from "utils/db.ts";
+
+export const handler: Handlers = {
+  async POST(req: Request) {
+    const reqJson = await req.json();
+    const id = reqJson.id;
+    const shared = reqJson.shared;
+    const tokenUserId = checkToken(req);
+    if (tokenUserId && id) {
+      const post = find("Post", {
+        id,
+        user_id: tokenUserId,
+      });
+      if (post.length > 0) {
+        const newPost = update("Post", id, {
+          shared: Boolean(shared),
+        });
+        if (newPost.length > 0) {
+          return makeSuccessResponse(true);
+        }
+      }
+    }
+    return makeErrorResponse();
+  },
+};

+ 1 - 1
routes/index.tsx

@@ -10,7 +10,7 @@ import PostList from "../islands/PostList.tsx";
 
 interface HomeProps {
   name: string;
-  list: { id: number; title: string; content: string; shared: boolean }[];
+  list: { id: string; title: string; content: string; shared: boolean }[];
 }
 
 export const handler: Handlers<HomeProps> = {

+ 48 - 4
static/global.css

@@ -21,7 +21,7 @@
 }
 
 /* Global form styles start */
-input,
+input:not([type="radio"]):not([type="checkbox"]),
 textarea {
   width: 100%;
   display: block;
@@ -32,12 +32,17 @@ textarea {
   outline: none;
 }
 
-input {
+input:not([type="radio"]):not([type="checkbox"]) {
   height: 38px;
   line-height: 30px;
   padding: 4px 0.375rem;
 }
 
+input[type="checkbox"] {
+  width: 16px;
+  height: 16px;
+}
+
 textarea {
   height: 100%;
   padding: 0.375rem;
@@ -127,9 +132,36 @@ button {
   background-color: #fff;
   border: 1px solid #dee2e6;
   border-radius: 0.5rem;
-  padding: 1rem;
-  max-width: 500px;
+  width: 500px;
+  max-width: 90%;
   max-height: 60%;
+  position: relative;
+  font-size: 16px;
+  cursor: pointer;
+}
+
+.pd-modal .pd-modal-content .pd-modal-close {
+  position: absolute;
+  right: 1rem;
+  top: 0.75rem;
+  font-size: 1.5rem;
+}
+
+.pd-modal .pd-modal-content .pd-modal-title {
+  padding: 1rem;
+  border-bottom: 1px solid #dee2e6;
+  font-weight: 500;
+}
+
+.pd-modal .pd-modal-content .pd-modal-body {
+  padding: 1rem;
+}
+
+.pd-modal .pd-modal-content .pd-modal-footer {
+  display: flex;
+  justify-content: flex-end;
+  border-top: 1px solid #dee2e6;
+  padding: 1rem;
 }
 /* Modal styles end */
 
@@ -182,6 +214,10 @@ button {
   font-size: 14px;
 }
 
+.pd-top-bar .pd-top-bar-mode-switcher.hidden {
+  display: none;
+}
+
 .pd-top-bar .pd-top-bar-mode-switcher .pd-top-bar-btn {
   border: none;
 }
@@ -215,6 +251,14 @@ button {
   border-radius: 0;
 }
 
+.pd-top-bar .pd-top-bar-title {
+  line-height: 30px;
+  font-size: 24px;
+  font-weight: 500;
+  text-align: center;
+  width: 100%;
+}
+
 .pd-top-bar .pd-top-bar-tool-icons {
   height: 30px;
   line-height: 28px;