feat: settings

This commit is contained in:
Artemy 2023-04-03 17:03:01 +03:00
parent b250d27093
commit c789c914aa
14 changed files with 165 additions and 148 deletions

View file

@ -23,6 +23,9 @@ app.post("/publish", function (req, res) {
if (isValidNote(req.body)) { if (isValidNote(req.body)) {
let hash = sha3(JSON.stringify(req.body)); let hash = sha3(JSON.stringify(req.body));
req.body.time = Date.now(); req.body.time = Date.now();
req.body.pub = true;
req.body.pubTime = req.body.time;
try { try {
fs.writeFileSync( fs.writeFileSync(
`./notes/${hash}.json`, `./notes/${hash}.json`,

View file

@ -11,6 +11,7 @@ import PubError from "./pages/pubError";
import PubNoteSafe from "./pages/pubNoteSafe"; import PubNoteSafe from "./pages/pubNoteSafe";
import RenderMarkdown from "./components/markdown"; import RenderMarkdown from "./components/markdown";
import socket from "./components/socket"; import socket from "./components/socket";
import Settings from "./pages/settings";
function App() { function App() {
Storage.prototype.setObj = function (key, obj) { Storage.prototype.setObj = function (key, obj) {
@ -20,6 +21,8 @@ function App() {
return JSON.parse(this.getItem(key)) || {}; return JSON.parse(this.getItem(key)) || {};
}; };
window.settings = localStorage.getObj("settings") || {};
return ( return (
<div className="grid grid-cols-4 lg:grid-cols-5 gap-10 text-black dark:text-white"> <div className="grid grid-cols-4 lg:grid-cols-5 gap-10 text-black dark:text-white">
<Menu /> <Menu />
@ -32,6 +35,7 @@ function App() {
<Route path="/pubNotes/:id" element={<PubNote />} /> <Route path="/pubNotes/:id" element={<PubNote />} />
<Route path="/pubNotesSafe/:id" element={<PubNoteSafe />} /> <Route path="/pubNotesSafe/:id" element={<PubNoteSafe />} />
<Route path="/pubError" element={<PubError />} /> <Route path="/pubError" element={<PubError />} />
<Route path="/settings" element={<Settings />} />
<Route <Route
path="/about" path="/about"
element={ element={

View file

@ -30,4 +30,25 @@ function IconWithText(props) {
); );
} }
export { Button, IconWithText }; function ButtonWithIcon(props) {
return (
<Button
href={props.href}
className={props.className}
onClick={props.onClick}
>
<IconWithText
reverse={props.reverse}
icon={
<props.icon
className={props.iconClass || "transform translate-z-0 h-7 w-7"}
/>
}
>
{props.text}
</IconWithText>
</Button>
);
}
export { Button, IconWithText, ButtonWithIcon };

View file

@ -1,4 +1,4 @@
import { Button, IconWithText } from "./button"; import { ButtonWithIcon } from "./button";
import { import {
MagnifyingGlassCircleIcon, MagnifyingGlassCircleIcon,
PencilIcon, PencilIcon,
@ -9,31 +9,18 @@ import {
function Menu() { function Menu() {
return ( return (
<div className="grid grid-cols-1 col-span-4 lg:col-span-1 gap-2 m-4 place-content-start justify-self-center justify-center"> <div className="grid grid-cols-1 col-span-4 lg:col-span-1 gap-2 m-4 place-content-start justify-self-center justify-center">
<Button href="/notes"> <ButtonWithIcon
<IconWithText icon={MagnifyingGlassCircleIcon}
icon={ text="Заметки"
<MagnifyingGlassCircleIcon className="transform translate-z-0 h-7 w-7" /> href="/notes"
} />
> <ButtonWithIcon icon={PencilIcon} text="Написать" href="/" />
Заметки <ButtonWithIcon icon={Cog6ToothIcon} text="Настройки" href="/settings" />
</IconWithText> <ButtonWithIcon
</Button> icon={ExclamationCircleIcon}
<Button href="/"> text="Подробнее"
<IconWithText href="/about"
icon={<PencilIcon className="transform translate-z-0 h-7 w-7" />} />
>
Написать
</IconWithText>
</Button>
<Button href="/about">
<IconWithText
icon={
<ExclamationCircleIcon className="transform translate-z-0 h-7 w-7" />
}
>
Подробнее
</IconWithText>
</Button>
</div> </div>
); );
} }

View file

@ -9,7 +9,9 @@ function Note({ note }) {
{note.name} {note.name}
</h2> </h2>
<div className="justify-self-center lg:justify-self-end"> <div className="justify-self-center lg:justify-self-end">
{printDate(note.time)} {`${printDate(note.time)} ${
note.pub ? "| Публичная" : "| Локальная"
}`}
</div> </div>
</div> </div>
<div className="w-full md break-words"> <div className="w-full md break-words">

View file

@ -0,0 +1,19 @@
import { CheckBox } from "./checkbox";
function SettingsCheckBox({ label, title, className, settingName, onClick }) {
return (
<CheckBox
label={label}
title={title}
checked={settings[settingName]}
className={className}
onClick={(e) => {
window.settings[settingName] = e.target.checked;
localStorage.setObj("settings", window.settings);
onClick(e);
}}
/>
);
}
export { SettingsCheckBox };

View file

@ -1,4 +1,4 @@
import { Button, IconWithText } from "../components/button"; import { ButtonWithIcon } from "../components/button";
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline"; import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
import { CheckBox } from "../components/checkbox"; import { CheckBox } from "../components/checkbox";
import { useState } from "react"; import { useState } from "react";
@ -12,21 +12,16 @@ import rehypeParse from "rehype-parse";
import remarkStringify from "remark-stringify"; import remarkStringify from "remark-stringify";
import remarkGfm from "remark-gfm"; import remarkGfm from "remark-gfm";
import remarkMath from "remark-math"; import remarkMath from "remark-math";
import { SettingsCheckBox } from "../components/settingsInputs";
function CreateNote() { function CreateNote() {
const [preview, setPreview] = useState(false); const [preview, setPreview] = useState(false);
const [publicState, setPublicState] = useState(true); const [publicState, setPublicState] = useState(settings.publicNote);
const [text, setText] = useState(localStorage.getItem("NoteText")); const [text, setText] = useState(localStorage.getItem("NoteText"));
const [name, setName] = useState(localStorage.getItem("NoteName")); const [name, setName] = useState(localStorage.getItem("NoteName"));
const [date, setDate] = useState(Date.now()); const [date, setDate] = useState(Date.now());
// setInterval(() => {
// if (preview) {
// setDate(Date.now());
// }
// }, 1000);
async function previewChange(val) { async function previewChange(val) {
let md = await unified() let md = await unified()
.use(remarkGfm) .use(remarkGfm)
@ -45,16 +40,17 @@ function CreateNote() {
return ( return (
<div> <div>
<div className="grid grid-cols-1 lg:grid-cols-2"> <div className="grid grid-cols-1 lg:grid-cols-2">
<h2 className="font-medium text-center lg:text-left leading-tight text-4xl mt-0 mb-2"> <h2 className="text-center lg:text-left leading-tight text-2xl font-bold">
{`${preview ? "" : "Написать заметку"}`} {`${preview ? "" : "Написать заметку"}`}
</h2> </h2>
<CheckBox <CheckBox
className="justify-self-center lg:justify-self-end" className="justify-self-center lg:justify-self-end"
label="Предпросмотр" label="Предпросмотр"
id="preview" id="preview"
onClick={() => { onClick={(val) => {
setText(localStorage.getItem("NoteText")); setText(localStorage.getItem("NoteText"));
setPreview(!preview); setDate(Date.now());
setPreview(val.target.checked);
}} }}
/> />
</div> </div>
@ -101,7 +97,7 @@ function CreateNote() {
{preview && ( {preview && (
<div className="w-full md break-words"> <div className="w-full md break-words">
<ContentEditable <ContentEditable
disabled={false} disabled={!window.settings.editPreview}
onChange={previewChange} onChange={previewChange}
html={ReactDOMServer.renderToString( html={ReactDOMServer.renderToString(
<RenderMarkdown>{text}</RenderMarkdown> <RenderMarkdown>{text}</RenderMarkdown>
@ -111,30 +107,24 @@ function CreateNote() {
)} )}
<div className="grid grid-cols-1 lg:grid-cols-2 justify-items-center w-full"> <div className="grid grid-cols-1 lg:grid-cols-2 justify-items-center w-full">
<CheckBox <SettingsCheckBox
className="justify-self-center lg:justify-self-start"
label="Публичная заметка" label="Публичная заметка"
title="Если включено, то заметка будет видна всем пользователям" title="Если включено, то заметка будет видна всем пользователям"
id="public" checked={settings.publicNote}
onClick={() => { settingName="publicNote"
setPublicState(!publicState); className="justify-self-center lg:justify-self-start"
onClick={(val) => {
setPublicState(val.target.checked);
}} }}
checked={localStorage.getItem("private")}
/> />
<div className="justify-self-center lg:justify-self-end"> <div className="justify-self-center lg:justify-self-end">
<Button <ButtonWithIcon
className="m-5" icon={ChevronDoubleRightIcon}
href={publicState ? "/notes/save-local" : "/notes/publish"} text={publicState ? "Опубликовать" : "Сохранить"}
>
<IconWithText
reverse={true} reverse={true}
icon={ href={publicState ? "/notes/publish" : "/notes/save-local"}
<ChevronDoubleRightIcon className="transform translate-z-0 h-7 w-7" /> className="m-1"
} />
>
{publicState ? "Сохранить" : "Опубликовать"}
</IconWithText>
</Button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { ChevronDoubleLeftIcon, TrashIcon } from "@heroicons/react/24/outline"; import { ChevronDoubleLeftIcon, TrashIcon } from "@heroicons/react/24/outline";
import { Button, IconWithText } from "../components/button"; import { ButtonWithIcon } from "../components/button";
import Note from "../components/note"; import Note from "../components/note";
function NotePage() { function NotePage() {
@ -8,25 +8,24 @@ function NotePage() {
let note = localStorage.getObj("Notes")[params.id]; let note = localStorage.getObj("Notes")[params.id];
if (note) {
return ( return (
<div className=""> <div className="">
<Button className="mb-4" href="/notes"> <ButtonWithIcon
<IconWithText icon={ChevronDoubleLeftIcon}
icon={ className="mb-4"
<ChevronDoubleLeftIcon className="transform translate-z-0 h-7 w-7" /> href="/notes"
} text="Заметки"
> />
Заметки
</IconWithText>
</Button>
<Note note={note} /> {note ? <Note note={note} /> : <div>Заметки не существует.</div>}
{note && (
<div className="grid grid-cols-1"> <div className="grid grid-cols-1">
<div className="justify-self-center lg:justify-self-end"> <div className="justify-self-center lg:justify-self-end">
<Button <ButtonWithIcon
className="mt-4" className="mt-4"
href="/notes" href="/notes"
text="Удалить"
icon={TrashIcon}
onClick={() => { onClick={() => {
let notesObj = localStorage.getObj("Notes"); let notesObj = localStorage.getObj("Notes");
@ -34,33 +33,12 @@ function NotePage() {
localStorage.setObj("Notes", notesObj); localStorage.setObj("Notes", notesObj);
}} }}
> />
<IconWithText
icon={<TrashIcon className="transform translate-z-0 h-7 w-7" />}
>
Удалить
</IconWithText>
</Button>
</div> </div>
</div> </div>
)}
</div> </div>
); );
} else {
return (
<div>
<Button className="mb-4" href="/notes">
<IconWithText
icon={
<ChevronDoubleLeftIcon className="transform translate-z-0 h-7 w-7" />
}
>
Заметки
</IconWithText>
</Button>
<div>Заметки не существует.</div>
</div>
);
}
} }
export default NotePage; export default NotePage;

View file

@ -1,4 +1,4 @@
import { Button, IconWithText } from "../components/button"; import { ButtonWithIcon } from "../components/button";
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline"; import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
import printDate from "../components/utils"; import printDate from "../components/utils";
@ -19,16 +19,12 @@ function Notes() {
<div className="grid grid-cols-1 lg:grid-cols-2 justify-self-center lg:justify-self-end"> <div className="grid grid-cols-1 lg:grid-cols-2 justify-self-center lg:justify-self-end">
<div className="text-center">{printDate(val[1].time)}</div> <div className="text-center">{printDate(val[1].time)}</div>
<div className=""> <div className="">
<Button className="" href={`/notes/${val[0]}`}> <ButtonWithIcon
<IconWithText href={`/notes/${val[0]}`}
reverse={true} reverse={true}
icon={ icon={ChevronDoubleRightIcon}
<ChevronDoubleRightIcon className="transform translate-z-0 h-7 w-7" /> text="Перейти"
} />
>
Перейти
</IconWithText>
</Button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
import printDate from "../components/utils"; import printDate from "../components/utils";
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline"; import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline";
import { Button, IconWithText } from "../components/button"; import { ButtonWithIcon } from "../components/button";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
function PubError() { function PubError() {
@ -9,15 +9,13 @@ function PubError() {
return ( return (
<div className=""> <div className="">
<Button className="mb-4" href="/"> <ButtonWithIcon
<IconWithText className="mb-4"
icon={ href="/"
<ChevronDoubleLeftIcon className="transform translate-z-0 h-7 w-7" /> text="Вернуться"
} icon={ChevronDoubleLeftIcon}
> />
Вернуться
</IconWithText>
</Button>
<div className="border border-blue-300 rounded-lg p-4"> <div className="border border-blue-300 rounded-lg p-4">
<div className="grid grid-cols-1 lg:grid-cols-2"> <div className="grid grid-cols-1 lg:grid-cols-2">
<h2 className="font-medium text-center lg:text-left leading-tight text-4xl mt-0 mb-2"> <h2 className="font-medium text-center lg:text-left leading-tight text-4xl mt-0 mb-2">

View file

@ -3,7 +3,7 @@ import { useState } from "react";
import { Navigate, useParams } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import printDate from "../components/utils"; import printDate from "../components/utils";
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline"; import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline";
import { Button, IconWithText } from "../components/button"; import { ButtonWithIcon } from "../components/button";
function PubNote() { function PubNote() {
let params = useParams(); let params = useParams();
@ -37,26 +37,19 @@ function PubNote() {
}); });
else { else {
if (note.save !== false) { if (note.save !== false) {
localStorage.setItem("NotePubTime", note.time);
localStorage.setItem("NoteName", note.name); localStorage.setItem("NoteName", note.name);
localStorage.setItem( localStorage.setItem("NoteText", note.text);
"NoteText",
`*(публичная заметка) (была опубликована в ${printDate(
note.time
)})* \n${note.text}`
);
return <Navigate to="/notes/save-local" replace={true} />; return <Navigate to="/notes/save-local" replace={true} />;
} else { } else {
return ( return (
<div className=""> <div className="">
<Button className="mb-4" href="/"> <ButtonWithIcon
<IconWithText className="mb-4"
icon={ href="/"
<ChevronDoubleLeftIcon className="transform translate-z-0 h-7 w-7" /> text="Писать"
} icon={ChevronDoubleLeftIcon}
> />
Писать
</IconWithText>
</Button>
<div className="border border-blue-300 rounded-lg p-4"> <div className="border border-blue-300 rounded-lg p-4">
<div className="grid grid-cols-1 lg:grid-cols-2"> <div className="grid grid-cols-1 lg:grid-cols-2">
<h2 className="font-medium text-center lg:text-left leading-tight text-4xl mt-0 mb-2"> <h2 className="font-medium text-center lg:text-left leading-tight text-4xl mt-0 mb-2">

View file

@ -1,9 +1,7 @@
import RenderMarkdown from "../components/markdown";
import { useState } from "react"; import { useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import printDate from "../components/utils";
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline"; import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline";
import { Button, IconWithText } from "../components/button"; import { ButtonWithIcon } from "../components/button";
import { CopyToClipboard } from "../components/copytocb"; import { CopyToClipboard } from "../components/copytocb";
import Note from "../components/note"; import Note from "../components/note";
@ -41,15 +39,12 @@ function PubNoteSafe() {
return ( return (
<div className=""> <div className="">
<Button className="mb-4" href="/"> <ButtonWithIcon
<IconWithText className="mb-4"
icon={ href="/"
<ChevronDoubleLeftIcon className="transform translate-z-0 h-7 w-7" /> text="Писать"
} icon={ChevronDoubleLeftIcon}
> />
Писать
</IconWithText>
</Button>
{note?.code === 1 && ( {note?.code === 1 && (
<div className="p-4 mb-2"> <div className="p-4 mb-2">

View file

@ -13,6 +13,8 @@ function Save() {
let id = uuidv4(); let id = uuidv4();
let name = localStorage.getItem("NoteName"); let name = localStorage.getItem("NoteName");
let text = localStorage.getItem("NoteText"); let text = localStorage.getItem("NoteText");
let pubTime = Number(localStorage.getItem("NotePubTime"));
if (!name || !text) return <Navigate to={`/notes`} replace={true} />; if (!name || !text) return <Navigate to={`/notes`} replace={true} />;
let notesObj = localStorage.getObj("Notes"); let notesObj = localStorage.getObj("Notes");
@ -21,12 +23,15 @@ function Save() {
name, name,
text, text,
time: Date.now(), time: Date.now(),
pubTime,
pub: !!pubTime,
}; };
localStorage.setObj("Notes", notesObj); localStorage.setObj("Notes", notesObj);
localStorage.removeItem("NoteName"); localStorage.removeItem("NoteName");
localStorage.removeItem("NoteText"); localStorage.removeItem("NoteText");
localStorage.removeItem("NotePubTime");
return <Navigate to={`/notes/${id}`} replace={true} />; return <Navigate to={`/notes/${id}`} replace={true} />;
} }

26
src/pages/settings.jsx Normal file
View file

@ -0,0 +1,26 @@
import { SettingsCheckBox } from "../components/settingsInputs";
function Settings() {
return (
<div className="">
<h1 className="text-center lg:text-left leading-tight text-2xl font-bold">
Настройки
</h1>
<SettingsCheckBox
label="Редактирование в предпросмотре"
title="Может вызывать необратимые изменения текста, например ломает теги кода"
checked={settings.editPreview}
settingName="editPreview"
/>
<SettingsCheckBox
label="Публичная заметка"
title="Если включено, то заметка будет видна всем пользователям"
checked={settings.publicNote}
settingName="publicNote"
/>
</div>
);
}
export default Settings;