TopBar.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import { useEffect, useState } from "preact/hooks";
  2. import { EditorMode } from "./Editor.tsx";
  3. interface TopBarProps {
  4. allowMode: EditorMode;
  5. isLogined: boolean;
  6. shared: boolean;
  7. title: string;
  8. id: string;
  9. }
  10. const settingsData: { [key: string]: string } = {};
  11. const shareData: { [key: string]: boolean } = {};
  12. export default function TopBar(props: TopBarProps) {
  13. const [mode, setMode] = useState(props.allowMode);
  14. const doLogout = async () => {
  15. const resp = await fetch("/api/user/logout");
  16. const respJson = await resp.json();
  17. if (respJson.success) {
  18. location.href = "/login";
  19. return true;
  20. }
  21. return false;
  22. };
  23. const goHome = () => {
  24. location.href = "/";
  25. };
  26. const showShare = () => {
  27. shareData["shared"] = shareData["submittedShared"] !== undefined
  28. ? shareData["submittedShared"]
  29. : props.shared;
  30. globalThis.$modal?.show(
  31. "Share options",
  32. <div style="display: flex; align-items: center">
  33. <input
  34. type="checkbox"
  35. checked={shareData["shared"]}
  36. onChange={(e) => {
  37. shareData["shared"] = (e.target as HTMLInputElement).checked;
  38. (
  39. document.querySelector("#shared-button") as HTMLButtonElement
  40. ).style.visibility = shareData["shared"] ? "visible" : "hidden";
  41. }}
  42. />
  43. <span style="margin: 0 8px">Shared to friends</span>
  44. <button
  45. type="button"
  46. id="shared-button"
  47. style={`${!shareData["shared"] ? ";visibility: hidden" : ""}`}
  48. onClick={async () => {
  49. await navigator.clipboard.writeText(location.href.split("?")[0]);
  50. }}
  51. >
  52. Copy Link
  53. </button>
  54. </div>,
  55. [
  56. {
  57. text: "Confirm",
  58. onClick: async () => {
  59. const resp = await fetch("/api/share", {
  60. method: "POST",
  61. headers: { "Content-Type": "application/json" },
  62. body: JSON.stringify({
  63. id: props.id,
  64. shared: shareData["shared"],
  65. }),
  66. });
  67. const respJson = await resp.json();
  68. if (respJson.success) {
  69. shareData["submittedShared"] = shareData["shared"];
  70. globalThis.$modal?.hide();
  71. }
  72. },
  73. },
  74. ],
  75. );
  76. };
  77. const showSetting = () => {
  78. settingsData["title"] = settingsData["submittedTitle"] || props.title;
  79. globalThis.$modal?.show(
  80. "Post Settings",
  81. <div style="display: flex; align-items: center">
  82. <span style="margin-right: 8px">Title</span>
  83. <input
  84. placeholder="Post title here"
  85. value={settingsData["title"]}
  86. onInput={(e) => {
  87. settingsData["title"] = (e.target as HTMLInputElement).value;
  88. }}
  89. />
  90. </div>,
  91. [
  92. {
  93. text: "Confirm",
  94. onClick: async () => {
  95. const resp = await fetch("/api/post", {
  96. method: "PUT",
  97. headers: { "Content-Type": "application/json" },
  98. body: JSON.stringify({
  99. id: props.id,
  100. title: settingsData["title"],
  101. }),
  102. });
  103. const respJson = await resp.json();
  104. if (respJson.success) {
  105. settingsData["submittedTitle"] = settingsData["title"];
  106. document.title = settingsData["title"];
  107. globalThis.$modal?.hide();
  108. }
  109. },
  110. },
  111. ],
  112. );
  113. };
  114. // Event listener
  115. const modeChangeListener = (e: Event) => {
  116. const { detail } = e as CustomEvent;
  117. if (
  118. detail &&
  119. (props.allowMode === detail || props.allowMode === EditorMode.Both)
  120. ) {
  121. setMode(detail);
  122. }
  123. };
  124. // Event dispatcher
  125. const modeChangeDispatcher = (mode: EditorMode) => {
  126. dispatchEvent(new CustomEvent("ModeChange", { detail: mode }));
  127. };
  128. // Init event listeners
  129. useEffect(() => {
  130. addEventListener("ModeChange", modeChangeListener);
  131. return () => {
  132. removeEventListener("ModeChange", modeChangeListener);
  133. };
  134. }, []);
  135. return (
  136. <div className="pd-top-bar">
  137. <div
  138. className={`pd-top-bar-mode-switcher${
  139. props.allowMode !== EditorMode.Both ? " hidden" : ""
  140. }`}
  141. >
  142. <button
  143. className={`pd-top-bar-btn${
  144. mode === EditorMode.Edit ? " active" : ""
  145. }`}
  146. id="edit"
  147. type="button"
  148. onClick={() => {
  149. modeChangeDispatcher(EditorMode.Edit);
  150. }}
  151. >
  152. Edit
  153. </button>
  154. <button
  155. className={`pd-top-bar-btn${
  156. mode === EditorMode.Read ? " active" : ""
  157. }`}
  158. id="read"
  159. type="button"
  160. onClick={() => {
  161. modeChangeDispatcher(EditorMode.Read);
  162. }}
  163. >
  164. Read
  165. </button>
  166. <button
  167. className={`pd-top-bar-btn${
  168. mode === EditorMode.Both ? " active" : ""
  169. }`}
  170. id="both"
  171. type="button"
  172. onClick={() => {
  173. modeChangeDispatcher(EditorMode.Both);
  174. }}
  175. >
  176. Both
  177. </button>
  178. </div>
  179. {!props.isLogined
  180. ? <span className="pd-top-bar-title">{props.title}</span>
  181. : null}
  182. {props.isLogined
  183. ? (
  184. <div className="pd-top-bar-tool-icons">
  185. <i className="bi bi-box-arrow-left" onClick={doLogout} />
  186. <i className="bi bi-house-door" onClick={goHome} />
  187. <i className="bi bi-share" onClick={showShare} />
  188. <i className="bi bi-gear" onClick={showSetting} />
  189. </div>
  190. )
  191. : null}
  192. </div>
  193. );
  194. }