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)) {
let hash = sha3(JSON.stringify(req.body));
req.body.time = Date.now();
req.body.pub = true;
req.body.pubTime = req.body.time;
try {
fs.writeFileSync(
`./notes/${hash}.json`,

View file

@ -11,6 +11,7 @@ import PubError from "./pages/pubError";
import PubNoteSafe from "./pages/pubNoteSafe";
import RenderMarkdown from "./components/markdown";
import socket from "./components/socket";
import Settings from "./pages/settings";
function App() {
Storage.prototype.setObj = function (key, obj) {
@ -20,6 +21,8 @@ function App() {
return JSON.parse(this.getItem(key)) || {};
};
window.settings = localStorage.getObj("settings") || {};
return (
<div className="grid grid-cols-4 lg:grid-cols-5 gap-10 text-black dark:text-white">
<Menu />
@ -32,6 +35,7 @@ function App() {
<Route path="/pubNotes/:id" element={<PubNote />} />
<Route path="/pubNotesSafe/:id" element={<PubNoteSafe />} />
<Route path="/pubError" element={<PubError />} />
<Route path="/settings" element={<Settings />} />
<Route
path="/about"
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 {
MagnifyingGlassCircleIcon,
PencilIcon,
@ -9,31 +9,18 @@ import {
function Menu() {
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">
<Button href="/notes">
<IconWithText
icon={
<MagnifyingGlassCircleIcon className="transform translate-z-0 h-7 w-7" />
}
>
Заметки
</IconWithText>
</Button>
<Button href="/">
<IconWithText
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>
<ButtonWithIcon
icon={MagnifyingGlassCircleIcon}
text="Заметки"
href="/notes"
/>
<ButtonWithIcon icon={PencilIcon} text="Написать" href="/" />
<ButtonWithIcon icon={Cog6ToothIcon} text="Настройки" href="/settings" />
<ButtonWithIcon
icon={ExclamationCircleIcon}
text="Подробнее"
href="/about"
/>
</div>
);
}

View file

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

View file

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

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 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="text-center">{printDate(val[1].time)}</div>
<div className="">
<Button className="" href={`/notes/${val[0]}`}>
<IconWithText
<ButtonWithIcon
href={`/notes/${val[0]}`}
reverse={true}
icon={
<ChevronDoubleRightIcon className="transform translate-z-0 h-7 w-7" />
}
>
Перейти
</IconWithText>
</Button>
icon={ChevronDoubleRightIcon}
text="Перейти"
/>
</div>
</div>
</div>

View file

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

View file

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

View file

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

View file

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