more explanation and formatting

This commit is contained in:
lingdocs 2022-02-10 15:31:38 +04:00
parent d377c26c6e
commit 6e33aa9ae3
7 changed files with 399 additions and 33 deletions

View File

@ -6,15 +6,20 @@ const requiredFields = [
"title",
];
const possibleFields = [
const suggestedFields = [
"author",
]
const otherFields = [
"date",
"description",
"rights",
"belongs-to-collection",
"author",
"editor",
"translator",
]
];
const possibleFields = [...suggestedFields, ...otherFields];
type Option = {
value: string,
@ -28,8 +33,8 @@ const baseSettings = {
function BookInfoInput({ handleSubmit }: { handleSubmit: (info: { frontmatter: Frontmatter, cover: File | undefined }) => void }) {
const coverRef = useRef<any>(null);
const [fieldsChosen, setFieldsChosen] = useState<string[]>([]);
const [state, setState] = useState<Frontmatter>(Object.assign({}, ...requiredFields.map(f => ({ [f]: "" }))));
const [fieldsChosen, setFieldsChosen] = useState<string[]>(suggestedFields);
const [state, setState] = useState<Frontmatter>({});
const fields = [...requiredFields, ...fieldsChosen];
const availableFields = possibleFields.filter(f => !fieldsChosen.includes(f));
const availableFieldsOptions = availableFields.map((f): Option => ({
@ -71,15 +76,18 @@ function BookInfoInput({ handleSubmit }: { handleSubmit: (info: { frontmatter: F
}
function submit() {
const cover = coverRef.current.files[0] as (File | undefined);
const frontmatter = {
...state,
...baseSettings,
};
console.log({ frontmatter });
return;
handleSubmit({
frontmatter: {
...state,
...baseSettings,
},
frontmatter,
cover,
});
}
return <div style={{ maxWidth: "500px" }}>
return <div style={{ maxWidth: "600px" }}>
<div className="my-3">
<label htmlFor="cover-file" className="form-label">cover image <span className="text-muted">(.jpg or .png less than 5mb)</span></label>
<input multiple={false} ref={coverRef} className="form-control" type="file" id="cover-file" accept="image/jpeg,image/png"/>
@ -94,7 +102,7 @@ function BookInfoInput({ handleSubmit }: { handleSubmit: (info: { frontmatter: F
</span>}
<span>{field}</span>
</label>
<input onChange={handleFieldChange} type="text" className="form-control" id={field} name={field} value={state[field]} />
<input onChange={handleFieldChange} type="text" className="form-control" id={field} name={field} value={state[field] || ""} />
</div>
))}
<div className="mt-4 mb-2">add fields:</div>
@ -110,7 +118,7 @@ function BookInfoInput({ handleSubmit }: { handleSubmit: (info: { frontmatter: F
options={availableFieldsOptions}
/>
<LanguageSelect value={state.lang} onChange={handleLanguageChange} />
<button onClick={submit} type="button" className="btn btn-lg btn-primary my-4">Create .epub</button>
<button onClick={submit} type="button" className="btn btn-lg btn-primary my-4">Download .epub</button>
</div>
}

View File

@ -1,15 +1,24 @@
import { useState } from "react";
import { useDropzone } from "react-dropzone";
import { uploadDoc } from "../lib/fetchers";
const textFormats = [
".doc", ".docx", ".md", ".txt", "text/*",
"application/vnd.oasis.opendocument.text", ".odt",
"application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".rtf"
]
function DocReceiver({ handleReceiveText }: {
handleReceiveText: (content: string) => void,
}) {
const [state, setState] = useState<string>("");
function onDrop(files: File[]) {
uploadDoc(files[0], {
start: () => null,
error: () => null,
progress: () => null,
error: () => setState("Error"),
progress: (p) => setState(p < 100 ? `Uploading ${p}%...` : "Processing..."),
complete: (m: string) => {
setState("");
handleReceiveText(m);
}
})
@ -17,11 +26,13 @@ function DocReceiver({ handleReceiveText }: {
const {getRootProps, getInputProps, isDragActive} = useDropzone({
onDrop,
multiple: false,
// accept: [".doc", ".docx", ".md", ".txt", "text/*", ""],
accept: textFormats,
});
return <div {...getRootProps()} className="clickable d-flex flex-row align-items-center justify-content-center" style={{ padding: "2rem 1rem", border: "2px dashed grey", textAlign: "center", backgroundColor: isDragActive ? "#34a8eb" : "inherit" }}>
<input {...getInputProps()} />
<div className="text-muted">Add Text/Markdown File or Word Doc</div>
<div className="text-muted">
{state ? state : "Drag file or click to add text file/document"}
</div>
</div>;
}

View File

@ -0,0 +1,52 @@
import { Modal } from "react-bootstrap";
function FormatGuideModal(props: {
show: boolean,
onHide: () => void,
}) {
return (
<Modal
{...props}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
📖 Formatting Guide
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
You can format the text of the book using <a href="https://www.markdowntutorial.com/">markdown</a>. If you don't know how to use markdown you only need to know two things:
</p>
<ol>
<li>To make a <strong>chapter heading</strong> put a <samp># </samp> in front of the chapter title.</li>
<li>Leave an <strong>empty line between every paragraph</strong>.</li>
</ol>
<p>For example:</p>
<textarea
spellCheck="false"
className="form-control"
rows={15}
>{`# A Chapter Title
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
# Another Chapter
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
`}</textarea>
</Modal.Body>
<Modal.Footer>
<button type="button" className="btn btn-secondary" onClick={props.onHide}>
Close
</button>
</Modal.Footer>
</Modal>
);
}
export default FormatGuideModal;

View File

@ -11,10 +11,10 @@ export function bookRequest(req: {
content: string,
cover?: File,
}, callback: {
start: (upload: { cancel: () => void }) => void,
progress: (percentage: number) => void,
error: () => void,
}) {
complete: () => void,
}): { cancel: () => void } {
const formData = new FormData();
const xhr = new XMLHttpRequest();
xhr.responseType = "blob";
@ -24,6 +24,7 @@ export function bookRequest(req: {
formData.append("file", req.cover);
}
xhr.onload = () => {
callback.complete();
const url = window.URL.createObjectURL(xhr.response);
const a = document.createElement('a');
a.href = url;
@ -45,15 +46,14 @@ export function bookRequest(req: {
function cancel() {
xhr.abort();
}
callback.start({ cancel });
return { cancel };
}
export function uploadDoc(file: File, callback: {
start: (upload: { cancel: () => void }) => void,
progress: (percentage: number) => void,
error: () => void,
complete: (markdown: string) => void,
}) {
}): { cancel: () => void } {
const formData = new FormData();
const xhr = new XMLHttpRequest();
formData.append("file", file);
@ -79,5 +79,5 @@ export function uploadDoc(file: File, callback: {
function cancel() {
xhr.abort();
}
callback.start({ cancel });
return { cancel };
}

272
package-lock.json generated
View File

@ -10,6 +10,7 @@
"next": "12.0.10",
"next-connect": "^0.12.1",
"react": "17.0.2",
"react-bootstrap": "^2.1.2",
"react-dom": "17.0.2",
"react-dropzone": "^12.0.1",
"react-select": "^5.2.2",
@ -391,6 +392,58 @@
"node": ">= 8"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz",
"integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@react-aria/ssr": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.1.0.tgz",
"integrity": "sha512-RxqQKmE8sO7TGdrcSlHTcVzMP450hqowtBSd2bBS9oPlcokVkaGq28c3Rwa8ty5ctw4EBCjXqjP7xdcKMGDzug==",
"dependencies": {
"@babel/runtime": "^7.6.2"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1"
}
},
"node_modules/@restart/hooks": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.5.tgz",
"integrity": "sha512-tLGtY0aHeIfT7aPwUkvQuhIy3+q3w4iqmUzFLPlOAf/vNUacLaBt1j/S//jv/dQhenRh8jvswyMojCwmLvJw8A==",
"dependencies": {
"dequal": "^2.0.2"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@restart/ui": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.0.1.tgz",
"integrity": "sha512-hLAqltcAjQYtjGuHBHKyPpR3ScTxzdkSYNvniwBfN7rUDbYiHu/UZiI1hvV2idJeUvktRnz29l7W9BnNLHrG6Q==",
"dependencies": {
"@babel/runtime": "^7.13.16",
"@popperjs/core": "^2.10.1",
"@react-aria/ssr": "^3.0.1",
"@restart/hooks": "^0.4.0",
"@types/warning": "^3.0.0",
"dequal": "^2.0.2",
"dom-helpers": "^5.2.0",
"prop-types": "^15.7.2",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/@rushstack/eslint-patch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz",
@ -439,6 +492,11 @@
"@types/range-parser": "*"
}
},
"node_modules/@types/invariant": {
"version": "2.2.35",
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.35.tgz",
"integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -516,6 +574,11 @@
"@types/node": "*"
}
},
"node_modules/@types/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI="
},
"node_modules/@typescript-eslint/parser": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.11.0.tgz",
@ -884,6 +947,11 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -1025,6 +1093,14 @@
"node": ">= 0.4"
}
},
"node_modules/dequal": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz",
"integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==",
"engines": {
"node": ">=6"
}
},
"node_modules/dicer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
@ -1911,6 +1987,14 @@
"node": ">= 0.4"
}
},
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@ -2708,6 +2792,18 @@
"react-is": "^16.13.1"
}
},
"node_modules/prop-types-extra": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
"integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
"dependencies": {
"react-is": "^16.3.2",
"warning": "^4.0.0"
},
"peerDependencies": {
"react": ">=0.14.0"
}
},
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@ -2749,6 +2845,33 @@
"node": ">=0.10.0"
}
},
"node_modules/react-bootstrap": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.1.2.tgz",
"integrity": "sha512-E7PR13cVsEW70gw08BWplENwn6PHTshskOsQygZqyc65jQlsnr9MsmuW/lgzAN2OiMBnc0KaNpuZ/FohL7dchw==",
"dependencies": {
"@babel/runtime": "^7.14.0",
"@restart/hooks": "^0.4.5",
"@restart/ui": "^1.0.1",
"@types/invariant": "^2.2.33",
"@types/prop-types": "^15.7.3",
"@types/react": ">=16.14.8",
"@types/react-transition-group": "^4.4.1",
"@types/warning": "^3.0.0",
"classnames": "^2.3.1",
"dom-helpers": "^5.2.1",
"invariant": "^2.2.4",
"prop-types": "^15.7.2",
"prop-types-extra": "^1.1.0",
"react-transition-group": "^4.4.1",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@ -2783,6 +2906,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"node_modules/react-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.2.2.tgz",
@ -3306,6 +3434,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/uncontrollable": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
"integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
"dependencies": {
"@babel/runtime": "^7.6.3",
"@types/react": ">=16.9.11",
"invariant": "^2.2.4",
"react-lifecycles-compat": "^3.0.4"
},
"peerDependencies": {
"react": ">=15.0.0"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@ -3337,6 +3479,14 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -3656,6 +3806,44 @@
"fastq": "^1.6.0"
}
},
"@popperjs/core": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz",
"integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA=="
},
"@react-aria/ssr": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.1.0.tgz",
"integrity": "sha512-RxqQKmE8sO7TGdrcSlHTcVzMP450hqowtBSd2bBS9oPlcokVkaGq28c3Rwa8ty5ctw4EBCjXqjP7xdcKMGDzug==",
"requires": {
"@babel/runtime": "^7.6.2"
}
},
"@restart/hooks": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.5.tgz",
"integrity": "sha512-tLGtY0aHeIfT7aPwUkvQuhIy3+q3w4iqmUzFLPlOAf/vNUacLaBt1j/S//jv/dQhenRh8jvswyMojCwmLvJw8A==",
"requires": {
"dequal": "^2.0.2"
}
},
"@restart/ui": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.0.1.tgz",
"integrity": "sha512-hLAqltcAjQYtjGuHBHKyPpR3ScTxzdkSYNvniwBfN7rUDbYiHu/UZiI1hvV2idJeUvktRnz29l7W9BnNLHrG6Q==",
"requires": {
"@babel/runtime": "^7.13.16",
"@popperjs/core": "^2.10.1",
"@react-aria/ssr": "^3.0.1",
"@restart/hooks": "^0.4.0",
"@types/warning": "^3.0.0",
"dequal": "^2.0.2",
"dom-helpers": "^5.2.0",
"prop-types": "^15.7.2",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
}
},
"@rushstack/eslint-patch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz",
@ -3704,6 +3892,11 @@
"@types/range-parser": "*"
}
},
"@types/invariant": {
"version": "2.2.35",
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.35.tgz",
"integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg=="
},
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -3781,6 +3974,11 @@
"@types/node": "*"
}
},
"@types/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI="
},
"@typescript-eslint/parser": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.11.0.tgz",
@ -4028,6 +4226,11 @@
"supports-color": "^7.1.0"
}
},
"classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -4146,6 +4349,11 @@
"object-keys": "^1.0.12"
}
},
"dequal": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz",
"integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug=="
},
"dicer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
@ -4835,6 +5043,14 @@
"side-channel": "^1.0.4"
}
},
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"requires": {
"loose-envify": "^1.0.0"
}
},
"is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@ -5407,6 +5623,15 @@
"react-is": "^16.13.1"
}
},
"prop-types-extra": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
"integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
"requires": {
"react-is": "^16.3.2",
"warning": "^4.0.0"
}
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@ -5428,6 +5653,29 @@
"object-assign": "^4.1.1"
}
},
"react-bootstrap": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.1.2.tgz",
"integrity": "sha512-E7PR13cVsEW70gw08BWplENwn6PHTshskOsQygZqyc65jQlsnr9MsmuW/lgzAN2OiMBnc0KaNpuZ/FohL7dchw==",
"requires": {
"@babel/runtime": "^7.14.0",
"@restart/hooks": "^0.4.5",
"@restart/ui": "^1.0.1",
"@types/invariant": "^2.2.33",
"@types/prop-types": "^15.7.3",
"@types/react": ">=16.14.8",
"@types/react-transition-group": "^4.4.1",
"@types/warning": "^3.0.0",
"classnames": "^2.3.1",
"dom-helpers": "^5.2.1",
"invariant": "^2.2.4",
"prop-types": "^15.7.2",
"prop-types-extra": "^1.1.0",
"react-transition-group": "^4.4.1",
"uncontrollable": "^7.2.1",
"warning": "^4.0.3"
}
},
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@ -5453,6 +5701,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.2.2.tgz",
@ -5819,6 +6072,17 @@
"which-boxed-primitive": "^1.0.2"
}
},
"uncontrollable": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
"integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
"requires": {
"@babel/runtime": "^7.6.3",
"@types/react": ">=16.9.11",
"invariant": "^2.2.4",
"react-lifecycles-compat": "^3.0.4"
}
},
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@ -5847,6 +6111,14 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
"warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"requires": {
"loose-envify": "^1.0.0"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -12,6 +12,7 @@
"next": "12.0.10",
"next-connect": "^0.12.1",
"react": "17.0.2",
"react-bootstrap": "^2.1.2",
"react-dom": "17.0.2",
"react-dropzone": "^12.0.1",
"react-select": "^5.2.2",

View File

@ -1,9 +1,10 @@
import type { NextPage } from "next";
import { useRef } from "react";
import { useState, useRef } from "react";
import Head from "next/head";
import BookInfoInput from "../components/BookInfoInput";
import DocReceiver from "../components/DocReceiver";
import { bookRequest } from "../lib/fetchers";
import FormatGuideModal from "../components/FormatGuideModal";
// TODO: Make Title Required
// TODO: Have author field in there
@ -12,6 +13,8 @@ import { bookRequest } from "../lib/fetchers";
const Home: NextPage = () => {
const mdRef = useRef<any>(null);
const [showFormatGuide, setShowFormatGuide] = useState<boolean>(false);
const [submissionStatus, setSubmissionStatus] = useState<string>("");
function handleReceiveText(m: string) {
mdRef.current.value = m;
}
@ -28,18 +31,18 @@ const Home: NextPage = () => {
alert("Please enter a title for the book");
return;
}
setSubmissionStatus("");
bookRequest({
...info,
content,
}, {
// TODO: Implement progress display etc
start: () => null,
progress: () => null,
error: () => null,
complete: () => setSubmissionStatus("Done"),
progress: (p) => setSubmissionStatus(p < 100 ? `Uploading ${p}%...` : "Processing..."),
error: () => setSubmissionStatus("Error"),
});
}
return (
<div className="container" style={{ marginBottom: "50px" }}>
<div className="container" style={{ marginBottom: "50px", maxWidth: "950px" }}>
<Head>
<title>RTL EPUB Maker</title>
<meta name="description" content="Easily create EPUB e-book files with proper RTL support" />
@ -50,22 +53,41 @@ const Home: NextPage = () => {
<link rel="icon" href="/favicon.ico" />
</Head>
<h1 className="mt-3">RTL EPUB Maker 📚</h1>
<p className="lead mb-4">Easily create EPUB e-book files with proper RTL support (🚧 in progress 👷)</p>
<h4>Book Content</h4>
<p className="lead mb-4">Easily create EPUB e-book files with proper RTL support</p>
<h4 className="mb-3">Book Content</h4>
<DocReceiver handleReceiveText={handleReceiveText}/>
<div className="mt-3">
<label htmlFor="mdTextarea" className="form-label">Markdown Content</label>
<textarea spellCheck="false" dir="rtl" ref={mdRef} className="form-control" id="mdTextarea" rows={15} />
<label htmlFor="mdTextarea" className="form-label d-flex flex-row justify-content-between align-items-center">
<div>Text in Markdown</div>
<div>
<button type="button" className="btn btn-sm btn-light" onClick={() => setShowFormatGuide(true)}>
📖 Formatting Guide
</button>
</div>
</label>
<textarea
placeholder="or paste book content here..."
spellCheck="false"
dir="rtl"
ref={mdRef}
className="form-control"
id="mdTextarea"
rows={15}
/>
</div>
<div style={{ textAlign: "right" }}>
<button type="button" className="btn btn-sm btn-light mt-2" onClick={clearText}>Clear</button>
</div>
<h4>Book Metadata</h4>
<BookInfoInput handleSubmit={handleSubmit} />
<div>
<samp>{submissionStatus}</samp>
</div>
<div className="text-center mt-4 text-muted">
<p className="lead">Made by <a className="em-link" href="https://lingdocs.com">LingDocs.com</a></p>
<p>Submissions are private. Nothing is kept on the server. See the <a className="em-link" href="https://github.com/lingdocs/rtl-epub-maker">source code here</a>.</p>
</div>
<FormatGuideModal show={showFormatGuide} onHide={() => setShowFormatGuide(false)} />
</div>
)
}