Hook up API

This commit is contained in:
Tom Moor
2020-12-26 17:53:56 -08:00
parent 799e639439
commit 7021c2a9e5
4 changed files with 92 additions and 14 deletions

View File

@ -6,20 +6,47 @@ import Button from "components/Button";
import CenteredContent from "components/CenteredContent";
import HelpText from "components/HelpText";
import PageTitle from "components/PageTitle";
import VisuallyHidden from "components/VisuallyHidden";
import useCurrentUser from "hooks/useCurrentUser";
import useStores from "hooks/useStores";
import getDataTransferFiles from "utils/getDataTransferFiles";
function ImportExport() {
const { t } = useTranslation();
const user = useCurrentUser();
const { ui, collections } = useStores();
const fileRef = React.useRef();
const { ui, collections, documents } = useStores();
const { showToast } = ui;
const [isLoading, setLoading] = React.useState(false);
const [isImporting, setImporting] = React.useState(false);
const [isExporting, setExporting] = React.useState(false);
const handleImport = React.useCallback(async () => {
// TODO
}, []);
const handleFilePicked = React.useCallback(
async (ev) => {
const files = getDataTransferFiles(ev);
setImporting(true);
try {
const file = files[0];
await documents.batchImport(file);
showToast(t("Import completed"));
} catch (err) {
showToast(err.message);
} finally {
if (fileRef.current) {
fileRef.current.value = "";
}
setImporting(false);
}
},
[t, documents, showToast]
);
const handleImport = React.useCallback(() => {
if (fileRef.current) {
fileRef.current.click();
}
}, [fileRef]);
const handleExport = React.useCallback(
async (ev: SyntheticEvent<>) => {
@ -43,11 +70,26 @@ function ImportExport() {
<h1>{t("Import")}</h1>
<HelpText>
<Trans>
It is possible to import a zip file of folders and Markdown files.
It is possible to import a zip file of folders and Markdown files
previously exported from an Outline instance. Support will soon be
added for importing from other services.
</Trans>
</HelpText>
<Button type="submit" onClick={handleImport} primary>
{t("Import")}
<VisuallyHidden>
<input
type="file"
ref={fileRef}
onChange={handleFilePicked}
accept="application/zip"
/>
</VisuallyHidden>
<Button
type="submit"
onClick={handleImport}
disabled={isImporting}
primary
>
{isImporting ? `${t("Importing")}` : t("Import Data")}
</Button>
<h1>{t("Export")}</h1>

View File

@ -497,6 +497,15 @@ export default class DocumentsStore extends BaseStore<Document> {
return this.add(res.data);
};
@action
batchImport = async (file: File) => {
const formData = new FormData();
formData.append("type", "outline");
formData.append("file", file);
await client.post("/documents.batchImport", formData);
};
@action
import = async (
file: File,

View File

@ -5,6 +5,7 @@ import File from "formidable/lib/file";
import invariant from "invariant";
import JSZip from "jszip";
import { values, keys } from "lodash";
import { InvalidRequestError } from "../errors";
import { Attachment, Document, Collection, User } from "../models";
import attachmentCreator from "./attachmentCreator";
import documentCreator from "./documentCreator";
@ -33,20 +34,31 @@ export default async function documentBatchImporter({
// this is so we can use async / await a little easier
let folders = [];
zip.forEach(async function (path, item) {
// known skippable items
if (path.startsWith("__MACOSX") || path.endsWith(".DS_Store")) {
return;
}
folders.push([path, item]);
});
for (const [rawPath, item] of folders) {
const itemPath = rawPath.replace(/\/$/, "");
const depth = itemPath.split("/").length - 1;
if (depth === 0 && !item.dir) {
throw new InvalidRequestError(
"Root of zip file must only contain folders representing collections"
);
}
}
for (const [rawPath, item] of folders) {
const itemPath = rawPath.replace(/\/$/, "");
const itemDir = path.dirname(itemPath);
const name = path.basename(item.name);
const depth = itemPath.split("/").length - 1;
// known skippable items
if (itemPath.startsWith("__MACOSX") || itemPath.endsWith(".DS_Store")) {
continue;
}
if (depth === 0 && item.dir && name) {
// check if collection with name exists
let [collection, isCreated] = await Collection.findOrCreate({
@ -142,12 +154,17 @@ export default async function documentBatchImporter({
const attachment = attachments[attachmentPath];
for (const document of values(documents)) {
// pull the collection out of the path name
const pathParts = attachmentPath.split("/");
const normalizedAttachmentPath = pathParts.splice(1).join("/");
document.text = document.text
.replace(attachmentPath, attachment.redirectUrl)
.replace(`/${attachmentPath}`, attachment.redirectUrl);
.replace(normalizedAttachmentPath, attachment.redirectUrl)
.replace(`/${normalizedAttachmentPath}`, attachment.redirectUrl);
// does nothing if the document text is unchanged
await document.save();
await document.save({ fields: ["text"] });
}
}

View File

@ -275,6 +275,16 @@
"Use the <1>{{meta}}+K</1> shortcut to search from anywhere in your knowledge base": "Use the <1>{{meta}}+K</1> shortcut to search from anywhere in your knowledge base",
"No documents found for your search filters. <1></1>Create a new document?": "No documents found for your search filters. <1></1>Create a new document?",
"Clear filters": "Clear filters",
"Import completed": "Import completed",
"Export in progress…": "Export in progress…",
"Import": "Import",
"It is possible to import a zip file of folders and Markdown files previously exported from an Outline instance. Well soon add support for importing from other services.": "It is possible to import a zip file of folders and Markdown files previously exported from an Outline instance. Well soon add support for importing from other services.",
"Importing": "Importing",
"Import Data": "Import Data",
"A full export might take some time, consider exporting a single document or collection if possible. Well put together a zip of all your documents in Markdown format and email it to <2>{{userEmail}}</2>.": "A full export might take some time, consider exporting a single document or collection if possible. Well put together a zip of all your documents in Markdown format and email it to <2>{{userEmail}}</2>.",
"Export Requested": "Export Requested",
"Requesting Export": "Requesting Export",
"Export Data": "Export Data",
"Profile saved": "Profile saved",
"Profile picture updated": "Profile picture updated",
"Unable to upload new profile picture": "Unable to upload new profile picture",