mirror of
https://github.com/artegoser/AnoPaper.git
synced 2024-11-23 20:36:21 +03:00
Compare commits
10 commits
06e8f6ce41
...
bc0c9c3f02
Author | SHA1 | Date | |
---|---|---|---|
bc0c9c3f02 | |||
3963e435e8 | |||
f8365ce2ee | |||
32f1d78a71 | |||
6d17fca352 | |||
3f705bf8c4 | |||
80858f53d9 | |||
b8c7c69012 | |||
0a0f0f950a | |||
ff9eccc887 |
15 changed files with 239 additions and 122 deletions
|
@ -26,6 +26,7 @@ Running on: <https://anopaper.artegoser.ru/>
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Save notes locally
|
- Save notes locally
|
||||||
|
- Edit local notes
|
||||||
- Publish one-time notes (when read, the note is deleted from the server and saved locally)
|
- Publish one-time notes (when read, the note is deleted from the server and saved locally)
|
||||||
- Use OpenAI API to complete notes (with your own api key)
|
- Use OpenAI API to complete notes (with your own api key)
|
||||||
- Collaborate with other users on notes
|
- Collaborate with other users on notes
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
# Anopaper v1.1.0 (coming)
|
# Anopaper v1.1.0 (coming)
|
||||||
|
|
||||||
- [ ] Local notes editing
|
- [x] Local notes editing (0a0f0f950ae95afb78d3fe71815b351f77f01eb9)
|
||||||
- [ ] Publish local notes
|
- [x] Publish local notes (3963e435e8faca9b93b2e481ac799d78ed863f8c)
|
||||||
- [x] Migration notes storage to mongodb (#3)
|
- [x] Migration notes storage to mongodb (#3)
|
||||||
- [ ] Settings for publish notes, such as: delete after reading, number of reads before deleting, adding your own data (name, picture, status in the settings) to the note.
|
- [ ] Settings for publish notes, such as: delete after reading, number of reads before deleting, adding your own data (name, picture, status in the settings) to the note.
|
||||||
- [ ] Api for upload photos
|
- [ ] Api for upload photos
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import RenderMarkdown from "../components/markdown";
|
import RenderMarkdown from "../components/markdown";
|
||||||
import { printDate } from "./utils";
|
import { timestamp2text } from "./utils";
|
||||||
|
|
||||||
function Note({ note }) {
|
function Note({ note }) {
|
||||||
return (
|
return (
|
||||||
|
@ -26,7 +26,7 @@ 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)} ${
|
{`${timestamp2text(note.time)} ${
|
||||||
note.pub ? `| ${locals.PublicNote}` : `| ${locals.LocalNote}`
|
note.pub ? `| ${locals.PublicNote}` : `| ${locals.LocalNote}`
|
||||||
}`}
|
}`}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
|
|
||||||
import { Configuration, OpenAIApi } from "openai";
|
import { Configuration, OpenAIApi } from "openai";
|
||||||
|
|
||||||
async function Complete(setText, textUpdate) {
|
async function Complete(text) {
|
||||||
document.body.style.cursor = "wait";
|
document.body.style.cursor = "wait";
|
||||||
|
|
||||||
let initText = localStorage.getItem("NoteText");
|
let initText = text;
|
||||||
|
|
||||||
const configuration = new Configuration({
|
const configuration = new Configuration({
|
||||||
apiKey: settings.openAiKey,
|
apiKey: settings.openAiKey,
|
||||||
|
@ -37,16 +37,9 @@ async function Complete(setText, textUpdate) {
|
||||||
logprobs: null,
|
logprobs: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
let totalText = initText + response.data.choices[0].text;
|
|
||||||
|
|
||||||
localStorage.setItem("NoteText", totalText);
|
|
||||||
setText(totalText);
|
|
||||||
|
|
||||||
if (settings.CollabEdit === true) {
|
|
||||||
textUpdate(totalText, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.style.cursor = "default";
|
document.body.style.cursor = "default";
|
||||||
|
|
||||||
|
return initText + response.data.choices[0].text;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Complete };
|
export { Complete };
|
||||||
|
|
|
@ -14,9 +14,12 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
/* eslint-disable react-refresh/only-export-components */
|
||||||
import { CheckBox } from "./checkbox";
|
import { CheckBox } from "./checkbox";
|
||||||
import { inputStyle, settingsAddInput } from "./styles";
|
import { inputStyle, settingsAddInput } from "./styles";
|
||||||
|
import { ButtonWithIcon } from "./button";
|
||||||
|
import { Complete } from "../components/openai";
|
||||||
|
import { DocumentTextIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
function SettingsCheckBox({ label, title, className, settingName, onClick }) {
|
function SettingsCheckBox({ label, title, className, settingName, onClick }) {
|
||||||
return (
|
return (
|
||||||
|
@ -109,10 +112,80 @@ function SettingsSection({ name, children }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function NoteNameInput({ value, onChange, preview = false }) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className={`mb-2 md:w-1/6 w-full ${inputStyle} ${
|
||||||
|
preview ? "hidden" : ""
|
||||||
|
}`}
|
||||||
|
placeholder={locals.NoteName}
|
||||||
|
maxLength={64}
|
||||||
|
defaultValue={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NoteTextArea({ value, onChange, preview = false }) {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
className={`
|
||||||
|
${inputStyle}
|
||||||
|
w-full
|
||||||
|
${preview ? "hidden" : ""}
|
||||||
|
`}
|
||||||
|
rows="10"
|
||||||
|
placeholder={locals.NotePlaceholder}
|
||||||
|
maxLength={5000}
|
||||||
|
onChange={onChange}
|
||||||
|
defaultValue={value}
|
||||||
|
id="noteTextArea"
|
||||||
|
></textarea>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NotesAdditionalSettings({
|
||||||
|
noteText = localStorage.getItem("NoteText"),
|
||||||
|
onClick,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{settings.additionalFeatures && (
|
||||||
|
<div className="justify-self-start lg:justify-self-start">
|
||||||
|
<SettingsSection name={locals.AdditionalFeatures}>
|
||||||
|
{!!settings.openAiKey && (
|
||||||
|
<ButtonWithIcon
|
||||||
|
icon={DocumentTextIcon}
|
||||||
|
text={locals.AIComplete}
|
||||||
|
className="m-1"
|
||||||
|
w="w-full"
|
||||||
|
onClick={async () => {
|
||||||
|
let text = await Complete(noteText);
|
||||||
|
document.getElementById("noteTextArea").value = text;
|
||||||
|
|
||||||
|
onClick(text);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<SettingsCheckBox
|
||||||
|
label={locals.CollabEdit}
|
||||||
|
settingName="CollabEdit"
|
||||||
|
/>
|
||||||
|
</SettingsSection>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SettingsCheckBox,
|
SettingsCheckBox,
|
||||||
SettingsTextInput,
|
SettingsTextInput,
|
||||||
SettingsSelectInput,
|
SettingsSelectInput,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
setSetting,
|
setSetting,
|
||||||
|
NoteNameInput,
|
||||||
|
NoteTextArea,
|
||||||
|
NotesAdditionalSettings,
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import { Locales } from "../localisation/main";
|
import { Locales } from "../localisation/main";
|
||||||
|
|
||||||
function printDate(time) {
|
function timestamp2text(time) {
|
||||||
time = new Date(time);
|
time = new Date(time);
|
||||||
return time.toLocaleString(settings.language);
|
return time.toLocaleString(settings.language);
|
||||||
}
|
}
|
||||||
|
@ -48,4 +48,4 @@ async function getNetLocale(lang, fileName) {
|
||||||
return (await (await fetch(`localisation/${lang}/${fileName}`)).text()) || "";
|
return (await (await fetch(`localisation/${lang}/${fileName}`)).text()) || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export { printDate, reRenderPage, localesProcess, getNetLocale };
|
export { timestamp2text, reRenderPage, localesProcess, getNetLocale };
|
||||||
|
|
|
@ -75,6 +75,7 @@ let en = {
|
||||||
LocalNote: "Local",
|
LocalNote: "Local",
|
||||||
Menu: "Menu",
|
Menu: "Menu",
|
||||||
SourceCode: "Source code",
|
SourceCode: "Source code",
|
||||||
|
Edit: "Edit",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default en;
|
export default en;
|
||||||
|
|
|
@ -77,6 +77,7 @@ let ru = {
|
||||||
PublishNote: "Публичная",
|
PublishNote: "Публичная",
|
||||||
Menu: "Меню",
|
Menu: "Меню",
|
||||||
SourceCode: "Исходный код",
|
SourceCode: "Исходный код",
|
||||||
|
Edit: "Изменить",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ru;
|
export default ru;
|
||||||
|
|
|
@ -16,14 +16,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ButtonWithIcon } from "../components/button";
|
import { ButtonWithIcon } from "../components/button";
|
||||||
import {
|
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
|
||||||
ChevronDoubleRightIcon,
|
|
||||||
DocumentTextIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { CheckBox } from "../components/checkbox";
|
import { CheckBox } from "../components/checkbox";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import RenderMarkdown from "../components/markdown";
|
import RenderMarkdown from "../components/markdown";
|
||||||
import { printDate } from "../components/utils";
|
import { timestamp2text } from "../components/utils";
|
||||||
import rehypeRemark from "rehype-remark/lib";
|
import rehypeRemark from "rehype-remark/lib";
|
||||||
import ContentEditable from "react-contenteditable";
|
import ContentEditable from "react-contenteditable";
|
||||||
import ReactDOMServer from "react-dom/server";
|
import ReactDOMServer from "react-dom/server";
|
||||||
|
@ -33,11 +30,11 @@ 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 {
|
import {
|
||||||
|
NoteNameInput,
|
||||||
|
NoteTextArea,
|
||||||
|
NotesAdditionalSettings,
|
||||||
SettingsCheckBox,
|
SettingsCheckBox,
|
||||||
SettingsSection,
|
|
||||||
} from "../components/settingsInputs";
|
} from "../components/settingsInputs";
|
||||||
import { inputStyle } from "../components/styles";
|
|
||||||
import { Complete } from "../components/openai";
|
|
||||||
|
|
||||||
function nameUpdate(val, force) {
|
function nameUpdate(val, force) {
|
||||||
if (Date.now() - window.lastSocketUpdate > window.socketTimeout || force) {
|
if (Date.now() - window.lastSocketUpdate > window.socketTimeout || force) {
|
||||||
|
@ -127,13 +124,7 @@ function CreateNote() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
<NoteNameInput
|
||||||
type="text"
|
|
||||||
className={`mb-2 md:w-1/6 w-full ${inputStyle} ${
|
|
||||||
preview ? "hidden" : ""
|
|
||||||
}`}
|
|
||||||
placeholder={locals.NoteName}
|
|
||||||
maxLength={64}
|
|
||||||
value={localStorage.getItem("NoteName") || ""}
|
value={localStorage.getItem("NoteName") || ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setName(e.target.value);
|
setName(e.target.value);
|
||||||
|
@ -146,16 +137,11 @@ function CreateNote() {
|
||||||
}, window.socketTimeout);
|
}, window.socketTimeout);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
preview={preview}
|
||||||
/>
|
/>
|
||||||
<textarea
|
|
||||||
className={`
|
<NoteTextArea
|
||||||
${inputStyle}
|
value={localStorage.getItem("NoteText") || ""}
|
||||||
w-full
|
|
||||||
${preview ? "hidden" : ""}
|
|
||||||
`}
|
|
||||||
rows="10"
|
|
||||||
placeholder={locals.NotePlaceholder}
|
|
||||||
maxLength={5000}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setText(e.target.value);
|
setText(e.target.value);
|
||||||
localStorage.setItem("NoteText", e.target.value);
|
localStorage.setItem("NoteText", e.target.value);
|
||||||
|
@ -167,8 +153,8 @@ function CreateNote() {
|
||||||
}, window.socketTimeout);
|
}, window.socketTimeout);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
value={localStorage.getItem("NoteText") || ""}
|
preview={preview}
|
||||||
></textarea>
|
/>
|
||||||
|
|
||||||
{preview && (
|
{preview && (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2">
|
<div className="grid grid-cols-1 lg:grid-cols-2">
|
||||||
|
@ -176,7 +162,7 @@ function CreateNote() {
|
||||||
{name}
|
{name}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="justify-self-center lg:justify-self-end">
|
<div className="justify-self-center lg:justify-self-end">
|
||||||
{printDate(Date.now())}
|
{timestamp2text(Date.now())}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -213,27 +199,16 @@ function CreateNote() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{settings.additionalFeatures && (
|
<NotesAdditionalSettings
|
||||||
<div className="justify-self-start lg:justify-self-start">
|
onClick={(text) => {
|
||||||
<SettingsSection name={locals.AdditionalFeatures}>
|
localStorage.setItem("NoteText", text);
|
||||||
{!!settings.openAiKey && (
|
setText(text);
|
||||||
<ButtonWithIcon
|
|
||||||
icon={DocumentTextIcon}
|
if (settings.CollabEdit === true) {
|
||||||
text={locals.AIComplete}
|
textUpdate(text, true);
|
||||||
className="m-1"
|
}
|
||||||
w="w-full"
|
}}
|
||||||
onClick={() => {
|
/>
|
||||||
Complete(setText, textUpdate);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<SettingsCheckBox
|
|
||||||
label={locals.CollabEdit}
|
|
||||||
settingName="CollabEdit"
|
|
||||||
/>
|
|
||||||
</SettingsSection>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,14 +16,30 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { ChevronDoubleLeftIcon, TrashIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
|
ChevronDoubleLeftIcon,
|
||||||
|
ChevronDoubleRightIcon,
|
||||||
|
PencilIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
import { ButtonWithIcon } from "../components/button";
|
import { ButtonWithIcon } from "../components/button";
|
||||||
import Note from "../components/note";
|
import Note from "../components/note";
|
||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
NoteNameInput,
|
||||||
|
NoteTextArea,
|
||||||
|
NotesAdditionalSettings,
|
||||||
|
} from "../components/settingsInputs";
|
||||||
|
|
||||||
function NotePage() {
|
function NotePage() {
|
||||||
let params = useParams();
|
let params = useParams();
|
||||||
|
|
||||||
let note = localStorage.getObj("Notes")[params.id];
|
let notes = localStorage.getObj("Notes");
|
||||||
|
let note = notes[params.id];
|
||||||
|
|
||||||
|
let [edit, setEdit] = useState(false);
|
||||||
|
let [text, setText] = useState(note.text);
|
||||||
|
let [name, setName] = useState(note.name);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
|
@ -34,23 +50,71 @@ function NotePage() {
|
||||||
text={locals.Notes}
|
text={locals.Notes}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{note ? <Note note={note} /> : <div>{locals.NoteNotExists}</div>}
|
{note ? (
|
||||||
|
edit ? (
|
||||||
|
<>
|
||||||
|
<NoteNameInput
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<NoteTextArea
|
||||||
|
value={text}
|
||||||
|
onChange={(e) => setText(e.target.value)}
|
||||||
|
/>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 justify-items-center w-full">
|
||||||
|
<NotesAdditionalSettings
|
||||||
|
noteText={text}
|
||||||
|
onClick={(text) => {
|
||||||
|
setText(text);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Note note={note} />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<div>{locals.NoteNotExists}</div>
|
||||||
|
)}
|
||||||
{note && (
|
{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">
|
||||||
<ButtonWithIcon
|
<ButtonWithIcon
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
href="/notes"
|
text={edit ? locals.Save : locals.Edit}
|
||||||
text={locals.Delete}
|
icon={PencilIcon}
|
||||||
icon={TrashIcon}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let notesObj = localStorage.getObj("Notes");
|
if (edit) {
|
||||||
|
notes[params.id].name = name;
|
||||||
|
notes[params.id].text = text;
|
||||||
|
|
||||||
delete notesObj[params.id];
|
localStorage.setObj("Notes", notes);
|
||||||
|
}
|
||||||
|
|
||||||
localStorage.setObj("Notes", notesObj);
|
setEdit(!edit);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<ButtonWithIcon
|
||||||
|
className="mt-4"
|
||||||
|
text={locals.Publish}
|
||||||
|
icon={ChevronDoubleRightIcon}
|
||||||
|
href={`/notes/publish?local_id=${params.id}`}
|
||||||
|
/>
|
||||||
|
{!edit && (
|
||||||
|
<ButtonWithIcon
|
||||||
|
className="mt-4"
|
||||||
|
href="/notes"
|
||||||
|
text={locals.Delete}
|
||||||
|
icon={TrashIcon}
|
||||||
|
onClick={() => {
|
||||||
|
let notesObj = localStorage.getObj("Notes");
|
||||||
|
|
||||||
|
delete notesObj[params.id];
|
||||||
|
|
||||||
|
localStorage.setObj("Notes", notesObj);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import { ButtonWithIcon } 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 { timestamp2text } from "../components/utils";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import { inputStyle } from "../components/styles";
|
import { inputStyle } from "../components/styles";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
@ -30,7 +30,7 @@ function Notes() {
|
||||||
let notes = Object.entries(notesObj);
|
let notes = Object.entries(notesObj);
|
||||||
for (let [id, note] of notes) {
|
for (let [id, note] of notes) {
|
||||||
note.id = id;
|
note.id = id;
|
||||||
note.textTime = printDate(note.time);
|
note.textTime = timestamp2text(note.time);
|
||||||
notesObj[id] = note;
|
notesObj[id] = note;
|
||||||
}
|
}
|
||||||
localStorage.setObj("Notes", notesObj);
|
localStorage.setObj("Notes", notesObj);
|
||||||
|
@ -82,7 +82,7 @@ function Notes() {
|
||||||
{item.name}
|
{item.name}
|
||||||
</div>
|
</div>
|
||||||
<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(item.time)}</div>
|
<div className="text-center">{timestamp2text(item.time)}</div>
|
||||||
<div className="">
|
<div className="">
|
||||||
<ButtonWithIcon
|
<ButtonWithIcon
|
||||||
href={`/notes/${item.id}`}
|
href={`/notes/${item.id}`}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { printDate } from "../components/utils";
|
import { timestamp2text } from "../components/utils";
|
||||||
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline";
|
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline";
|
||||||
import { ButtonWithIcon } from "../components/button";
|
import { ButtonWithIcon } from "../components/button";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
@ -39,7 +39,7 @@ function PubError() {
|
||||||
{locals.PubError}
|
{locals.PubError}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="justify-self-center lg:justify-self-end">
|
<div className="justify-self-center lg:justify-self-end">
|
||||||
{printDate(Date.now())}
|
{timestamp2text(Date.now())}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full md">{err ? err : locals.PubErrorMsg}</div>
|
<div className="w-full md">{err ? err : locals.PubErrorMsg}</div>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
import RenderMarkdown from "../components/markdown";
|
import RenderMarkdown from "../components/markdown";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Navigate, useParams } from "react-router-dom";
|
import { Navigate, useParams } from "react-router-dom";
|
||||||
import { printDate } from "../components/utils";
|
import { timestamp2text } from "../components/utils";
|
||||||
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline";
|
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline";
|
||||||
import { ButtonWithIcon } from "../components/button";
|
import { ButtonWithIcon } from "../components/button";
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ function PubNote() {
|
||||||
{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 || Date.now())}
|
{timestamp2text(note.time || Date.now())}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full md break-words">
|
<div className="w-full md break-words">
|
||||||
|
|
|
@ -17,54 +17,63 @@
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
function Publish() {
|
function Publish() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
let done = false;
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!done) {
|
let err = false;
|
||||||
done = true;
|
|
||||||
|
|
||||||
let err = false;
|
let note;
|
||||||
const note = {
|
|
||||||
|
if (searchParams.get("local_id")) {
|
||||||
|
let localNote =
|
||||||
|
localStorage.getObj("Notes")[searchParams.get("local_id")];
|
||||||
|
note = {
|
||||||
|
name: localNote.name,
|
||||||
|
text: localNote.text,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
note = {
|
||||||
name: localStorage.getItem("NoteName"),
|
name: localStorage.getItem("NoteName"),
|
||||||
text: localStorage.getItem("NoteText"),
|
text: localStorage.getItem("NoteText"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!note.name) {
|
|
||||||
err = locals.PubErrorMsgNoName;
|
|
||||||
}
|
|
||||||
if (!note.text) {
|
|
||||||
err = locals.PubErrorMsgNoText;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(`/publish`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json;charset=utf-8",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(note),
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
data
|
|
||||||
.json()
|
|
||||||
.then((data) => {
|
|
||||||
localStorage.removeItem("NoteName");
|
|
||||||
localStorage.removeItem("NoteText");
|
|
||||||
navigate(`/pubNotesSafe/${data.id}`, { replace: true });
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (err == false) {
|
|
||||||
navigate(`/pubError`, { replace: true });
|
|
||||||
} else navigate(`/pubError?err=${err}`, { replace: true });
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
navigate(`/pubError`, { replace: true });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, []);
|
|
||||||
|
if (!note.name) {
|
||||||
|
err = locals.PubErrorMsgNoName;
|
||||||
|
}
|
||||||
|
if (!note.text) {
|
||||||
|
err = locals.PubErrorMsgNoText;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`/publish`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json;charset=utf-8",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(note),
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
data
|
||||||
|
.json()
|
||||||
|
.then((data) => {
|
||||||
|
localStorage.removeItem("NoteName");
|
||||||
|
localStorage.removeItem("NoteText");
|
||||||
|
navigate(`/pubNotesSafe/${data.id}`, { replace: true });
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (err == false) {
|
||||||
|
navigate(`/pubError`, { replace: true });
|
||||||
|
} else navigate(`/pubError?err=${err}`, { replace: true });
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
navigate(`/pubError`, { replace: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import { printDate } from "../components/utils";
|
import { timestamp2text } from "../components/utils";
|
||||||
|
|
||||||
function uuidv4() {
|
function uuidv4() {
|
||||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
|
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
|
||||||
|
@ -44,7 +44,7 @@ function Save() {
|
||||||
name,
|
name,
|
||||||
text,
|
text,
|
||||||
time,
|
time,
|
||||||
textTime: printDate(time),
|
textTime: timestamp2text(time),
|
||||||
pubTime,
|
pubTime,
|
||||||
pub: !!pubTime,
|
pub: !!pubTime,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue