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", "title",
]; ];
const possibleFields = [ const suggestedFields = [
"author",
]
const otherFields = [
"date", "date",
"description", "description",
"rights", "rights",
"belongs-to-collection", "belongs-to-collection",
"author",
"editor", "editor",
"translator", "translator",
] ];
const possibleFields = [...suggestedFields, ...otherFields];
type Option = { type Option = {
value: string, value: string,
@ -28,8 +33,8 @@ const baseSettings = {
function BookInfoInput({ handleSubmit }: { handleSubmit: (info: { frontmatter: Frontmatter, cover: File | undefined }) => void }) { function BookInfoInput({ handleSubmit }: { handleSubmit: (info: { frontmatter: Frontmatter, cover: File | undefined }) => void }) {
const coverRef = useRef<any>(null); const coverRef = useRef<any>(null);
const [fieldsChosen, setFieldsChosen] = useState<string[]>([]); const [fieldsChosen, setFieldsChosen] = useState<string[]>(suggestedFields);
const [state, setState] = useState<Frontmatter>(Object.assign({}, ...requiredFields.map(f => ({ [f]: "" })))); const [state, setState] = useState<Frontmatter>({});
const fields = [...requiredFields, ...fieldsChosen]; const fields = [...requiredFields, ...fieldsChosen];
const availableFields = possibleFields.filter(f => !fieldsChosen.includes(f)); const availableFields = possibleFields.filter(f => !fieldsChosen.includes(f));
const availableFieldsOptions = availableFields.map((f): Option => ({ const availableFieldsOptions = availableFields.map((f): Option => ({
@ -71,15 +76,18 @@ function BookInfoInput({ handleSubmit }: { handleSubmit: (info: { frontmatter: F
} }
function submit() { function submit() {
const cover = coverRef.current.files[0] as (File | undefined); const cover = coverRef.current.files[0] as (File | undefined);
handleSubmit({ const frontmatter = {
frontmatter: {
...state, ...state,
...baseSettings, ...baseSettings,
}, };
console.log({ frontmatter });
return;
handleSubmit({
frontmatter,
cover, cover,
}); });
} }
return <div style={{ maxWidth: "500px" }}> return <div style={{ maxWidth: "600px" }}>
<div className="my-3"> <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> <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"/> <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>}
<span>{field}</span> <span>{field}</span>
</label> </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>
))} ))}
<div className="mt-4 mb-2">add fields:</div> <div className="mt-4 mb-2">add fields:</div>
@ -110,7 +118,7 @@ function BookInfoInput({ handleSubmit }: { handleSubmit: (info: { frontmatter: F
options={availableFieldsOptions} options={availableFieldsOptions}
/> />
<LanguageSelect value={state.lang} onChange={handleLanguageChange} /> <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> </div>
} }

View File

@ -1,15 +1,24 @@
import { useState } from "react";
import { useDropzone } from "react-dropzone"; import { useDropzone } from "react-dropzone";
import { uploadDoc } from "../lib/fetchers"; 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 }: { function DocReceiver({ handleReceiveText }: {
handleReceiveText: (content: string) => void, handleReceiveText: (content: string) => void,
}) { }) {
const [state, setState] = useState<string>("");
function onDrop(files: File[]) { function onDrop(files: File[]) {
uploadDoc(files[0], { uploadDoc(files[0], {
start: () => null, error: () => setState("Error"),
error: () => null, progress: (p) => setState(p < 100 ? `Uploading ${p}%...` : "Processing..."),
progress: () => null,
complete: (m: string) => { complete: (m: string) => {
setState("");
handleReceiveText(m); handleReceiveText(m);
} }
}) })
@ -17,11 +26,13 @@ function DocReceiver({ handleReceiveText }: {
const {getRootProps, getInputProps, isDragActive} = useDropzone({ const {getRootProps, getInputProps, isDragActive} = useDropzone({
onDrop, onDrop,
multiple: false, 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" }}> 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()} /> <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>; </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, content: string,
cover?: File, cover?: File,
}, callback: { }, callback: {
start: (upload: { cancel: () => void }) => void,
progress: (percentage: number) => void, progress: (percentage: number) => void,
error: () => void, error: () => void,
}) { complete: () => void,
}): { cancel: () => void } {
const formData = new FormData(); const formData = new FormData();
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.responseType = "blob"; xhr.responseType = "blob";
@ -24,6 +24,7 @@ export function bookRequest(req: {
formData.append("file", req.cover); formData.append("file", req.cover);
} }
xhr.onload = () => { xhr.onload = () => {
callback.complete();
const url = window.URL.createObjectURL(xhr.response); const url = window.URL.createObjectURL(xhr.response);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
@ -45,15 +46,14 @@ export function bookRequest(req: {
function cancel() { function cancel() {
xhr.abort(); xhr.abort();
} }
callback.start({ cancel }); return { cancel };
} }
export function uploadDoc(file: File, callback: { export function uploadDoc(file: File, callback: {
start: (upload: { cancel: () => void }) => void,
progress: (percentage: number) => void, progress: (percentage: number) => void,
error: () => void, error: () => void,
complete: (markdown: string) => void, complete: (markdown: string) => void,
}) { }): { cancel: () => void } {
const formData = new FormData(); const formData = new FormData();
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
formData.append("file", file); formData.append("file", file);
@ -79,5 +79,5 @@ export function uploadDoc(file: File, callback: {
function cancel() { function cancel() {
xhr.abort(); xhr.abort();
} }
callback.start({ cancel }); return { cancel };
} }

272
package-lock.json generated
View File

@ -10,6 +10,7 @@
"next": "12.0.10", "next": "12.0.10",
"next-connect": "^0.12.1", "next-connect": "^0.12.1",
"react": "17.0.2", "react": "17.0.2",
"react-bootstrap": "^2.1.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-dropzone": "^12.0.1", "react-dropzone": "^12.0.1",
"react-select": "^5.2.2", "react-select": "^5.2.2",
@ -391,6 +392,58 @@
"node": ">= 8" "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": { "node_modules/@rushstack/eslint-patch": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz",
@ -439,6 +492,11 @@
"@types/range-parser": "*" "@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": { "node_modules/@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -516,6 +574,11 @@
"@types/node": "*" "@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": { "node_modules/@typescript-eslint/parser": {
"version": "5.11.0", "version": "5.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.11.0.tgz", "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" "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": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -1025,6 +1093,14 @@
"node": ">= 0.4" "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": { "node_modules/dicer": {
"version": "0.2.5", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
@ -1911,6 +1987,14 @@
"node": ">= 0.4" "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": { "node_modules/is-bigint": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@ -2708,6 +2792,18 @@
"react-is": "^16.13.1" "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": { "node_modules/punycode": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@ -2749,6 +2845,33 @@
"node": ">=0.10.0" "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": { "node_modules/react-dom": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "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", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "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": { "node_modules/react-select": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.2.2.tgz", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.2.2.tgz",
@ -3306,6 +3434,20 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/uri-js": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@ -3337,6 +3479,14 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true "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": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -3656,6 +3806,44 @@
"fastq": "^1.6.0" "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": { "@rushstack/eslint-patch": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz",
@ -3704,6 +3892,11 @@
"@types/range-parser": "*" "@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": { "@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -3781,6 +3974,11 @@
"@types/node": "*" "@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": { "@typescript-eslint/parser": {
"version": "5.11.0", "version": "5.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.11.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.11.0.tgz",
@ -4028,6 +4226,11 @@
"supports-color": "^7.1.0" "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": { "color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -4146,6 +4349,11 @@
"object-keys": "^1.0.12" "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": { "dicer": {
"version": "0.2.5", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
@ -4835,6 +5043,14 @@
"side-channel": "^1.0.4" "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": { "is-bigint": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@ -5407,6 +5623,15 @@
"react-is": "^16.13.1" "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": { "punycode": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@ -5428,6 +5653,29 @@
"object-assign": "^4.1.1" "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": { "react-dom": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "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", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "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": { "react-select": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.2.2.tgz", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.2.2.tgz",
@ -5819,6 +6072,17 @@
"which-boxed-primitive": "^1.0.2" "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": { "uri-js": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@ -5847,6 +6111,14 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true "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": { "which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

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

View File

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