Hook up API
This commit is contained in:
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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"] });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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. We’ll 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. We’ll 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. We’ll 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. We’ll 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",
|
||||
|
Reference in New Issue
Block a user