mirror of
https://github.com/artegoser/AnoPaper.git
synced 2024-11-05 20:43:57 +03:00
feat: settings
This commit is contained in:
parent
b250d27093
commit
c789c914aa
14 changed files with 165 additions and 148 deletions
3
index.js
3
index.js
|
@ -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`,
|
||||
|
|
|
@ -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={
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
19
src/components/settingsInputs.jsx
Normal file
19
src/components/settingsInputs.jsx
Normal 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 };
|
|
@ -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
|
||||
reverse={true}
|
||||
icon={
|
||||
<ChevronDoubleRightIcon className="transform translate-z-0 h-7 w-7" />
|
||||
}
|
||||
>
|
||||
{publicState ? "Сохранить" : "Опубликовать"}
|
||||
</IconWithText>
|
||||
</Button>
|
||||
<ButtonWithIcon
|
||||
icon={ChevronDoubleRightIcon}
|
||||
text={publicState ? "Опубликовать" : "Сохранить"}
|
||||
reverse={true}
|
||||
href={publicState ? "/notes/publish" : "/notes/save-local"}
|
||||
className="m-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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>
|
||||
return (
|
||||
<div className="">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotePage;
|
||||
|
|
|
@ -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
|
||||
reverse={true}
|
||||
icon={
|
||||
<ChevronDoubleRightIcon className="transform translate-z-0 h-7 w-7" />
|
||||
}
|
||||
>
|
||||
Перейти
|
||||
</IconWithText>
|
||||
</Button>
|
||||
<ButtonWithIcon
|
||||
href={`/notes/${val[0]}`}
|
||||
reverse={true}
|
||||
icon={ChevronDoubleRightIcon}
|
||||
text="Перейти"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
26
src/pages/settings.jsx
Normal 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;
|
Loading…
Reference in a new issue