Allow setting permission of collections during import (#6799)
* Allow setting permission of collections during import closes #6767 * Remove unused column
This commit is contained in:
@@ -6,9 +6,13 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { s } from "@shared/styles";
|
import { s } from "@shared/styles";
|
||||||
import { AttachmentPreset } from "@shared/types";
|
import { AttachmentPreset, CollectionPermission } from "@shared/types";
|
||||||
|
import { bytesToHumanReadable } from "@shared/utils/files";
|
||||||
|
import Button from "~/components/Button";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
|
import InputSelectPermission from "~/components/InputSelectPermission";
|
||||||
import LoadingIndicator from "~/components/LoadingIndicator";
|
import LoadingIndicator from "~/components/LoadingIndicator";
|
||||||
|
import Text from "~/components/Text";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import { uploadFile } from "~/utils/files";
|
import { uploadFile } from "~/utils/files";
|
||||||
|
|
||||||
@@ -23,38 +27,43 @@ type Props = {
|
|||||||
function DropToImport({ disabled, onSubmit, children, format }: Props) {
|
function DropToImport({ disabled, onSubmit, children, format }: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { collections } = useStores();
|
const { collections } = useStores();
|
||||||
|
const [file, setFile] = React.useState<File | null>(null);
|
||||||
const [isImporting, setImporting] = React.useState(false);
|
const [isImporting, setImporting] = React.useState(false);
|
||||||
|
const [permission, setPermission] =
|
||||||
|
React.useState<CollectionPermission | null>(CollectionPermission.ReadWrite);
|
||||||
|
|
||||||
const handleFiles = React.useCallback(
|
const handleFiles = (files: File[]) => {
|
||||||
async (files) => {
|
if (files.length > 1) {
|
||||||
if (files.length > 1) {
|
toast.error(t("Please choose a single file to import"));
|
||||||
toast.error(t("Please choose a single file to import"));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
setFile(files[0]);
|
||||||
const file = files[0];
|
};
|
||||||
|
|
||||||
setImporting(true);
|
const handleStartImport = async () => {
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setImporting(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const attachment = await uploadFile(file, {
|
const attachment = await uploadFile(file, {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
preset: AttachmentPreset.WorkspaceImport,
|
preset: AttachmentPreset.WorkspaceImport,
|
||||||
});
|
});
|
||||||
await collections.import(attachment.id, format);
|
await collections.import(attachment.id, { format, permission });
|
||||||
onSubmit();
|
onSubmit();
|
||||||
toast.message(file.name, {
|
toast.message(file.name, {
|
||||||
description: t(
|
description: t(
|
||||||
"Your import is being processed, you can safely leave this page"
|
"Your import is being processed, you can safely leave this page"
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message);
|
toast.error(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
setImporting(false);
|
setImporting(false);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
[t, onSubmit, collections, format]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRejection = React.useCallback(() => {
|
const handleRejection = React.useCallback(() => {
|
||||||
toast.error(t("File not supported – please upload a valid ZIP file"));
|
toast.error(t("File not supported – please upload a valid ZIP file"));
|
||||||
@@ -65,30 +74,53 @@ function DropToImport({ disabled, onSubmit, children, format }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Flex gap={8} column>
|
||||||
{isImporting && <LoadingIndicator />}
|
{isImporting && <LoadingIndicator />}
|
||||||
<Dropzone
|
<Text as="p" type="secondary">
|
||||||
accept="application/zip, application/x-zip-compressed"
|
<Dropzone
|
||||||
onDropAccepted={handleFiles}
|
accept="application/zip, application/x-zip-compressed"
|
||||||
onDropRejected={handleRejection}
|
onDropAccepted={handleFiles}
|
||||||
disabled={isImporting}
|
onDropRejected={handleRejection}
|
||||||
>
|
disabled={isImporting}
|
||||||
{({ getRootProps, getInputProps, isDragActive }) => (
|
>
|
||||||
<DropzoneContainer
|
{({ getRootProps, getInputProps, isDragActive }) => (
|
||||||
{...getRootProps()}
|
<DropzoneContainer
|
||||||
$disabled={isImporting}
|
{...getRootProps()}
|
||||||
$isDragActive={isDragActive}
|
$disabled={isImporting}
|
||||||
tabIndex={-1}
|
$isDragActive={isDragActive}
|
||||||
>
|
tabIndex={-1}
|
||||||
<input {...getInputProps()} />
|
>
|
||||||
<Flex align="center" gap={4} column>
|
<input {...getInputProps()} />
|
||||||
<Icon size={32} color="#fff" />
|
<Flex align="center" gap={4} column>
|
||||||
{children}
|
<Icon size={32} color="#fff" />
|
||||||
</Flex>
|
{file
|
||||||
</DropzoneContainer>
|
? t(`${file.name} (${bytesToHumanReadable(file.size)})`)
|
||||||
)}
|
: children}
|
||||||
</Dropzone>
|
</Flex>
|
||||||
</>
|
</DropzoneContainer>
|
||||||
|
)}
|
||||||
|
</Dropzone>
|
||||||
|
</Text>
|
||||||
|
<div>
|
||||||
|
<InputSelectPermission
|
||||||
|
value={permission}
|
||||||
|
onChange={(value: CollectionPermission) => {
|
||||||
|
setPermission(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text as="span" type="secondary">
|
||||||
|
{t(
|
||||||
|
"Set the default permission level for collections created from the import"
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<Flex justify="flex-end">
|
||||||
|
<Button disabled={!file} onClick={handleStartImport}>
|
||||||
|
{t("Start import")}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,29 +19,33 @@ const HelpDisclosure: React.FC<Props> = ({ title, children }: Props) => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Disclosure {...disclosure}>
|
<Disclosure {...disclosure}>
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Button
|
<StyledButton
|
||||||
icon={<QuestionMarkIcon color={theme.text} />}
|
icon={<QuestionMarkIcon color={theme.textSecondary} />}
|
||||||
neutral
|
neutral
|
||||||
|
aria-label={title}
|
||||||
borderOnHover
|
borderOnHover
|
||||||
{...props}
|
{...props}
|
||||||
>
|
/>
|
||||||
{title}
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
<HelpContent {...disclosure}>
|
<HelpContent {...disclosure}>
|
||||||
<Text as="p" type="secondary">
|
<Text as="p" type="secondary">
|
||||||
<br />
|
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
</HelpContent>
|
</HelpContent>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)`
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 50px;
|
||||||
|
`;
|
||||||
|
|
||||||
const HelpContent = styled(DisclosureContent)`
|
const HelpContent = styled(DisclosureContent)`
|
||||||
transition: opacity 250ms ease-in-out;
|
transition: opacity 250ms ease-in-out;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
import { FileOperationFormat } from "@shared/types";
|
import { FileOperationFormat } from "@shared/types";
|
||||||
import Flex from "~/components/Flex";
|
|
||||||
import Text from "~/components/Text";
|
|
||||||
import env from "~/env";
|
import env from "~/env";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import DropToImport from "./DropToImport";
|
import DropToImport from "./DropToImport";
|
||||||
@@ -13,18 +11,7 @@ function ImportJSONDialog() {
|
|||||||
const appName = env.APP_NAME;
|
const appName = env.APP_NAME;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex column>
|
<>
|
||||||
<Text as="p" type="secondary">
|
|
||||||
<DropToImport
|
|
||||||
onSubmit={dialogs.closeAllModals}
|
|
||||||
format={FileOperationFormat.JSON}
|
|
||||||
>
|
|
||||||
<Trans>
|
|
||||||
Drag and drop the zip file from the JSON export option in{" "}
|
|
||||||
{{ appName }}, or click to upload
|
|
||||||
</Trans>
|
|
||||||
</DropToImport>
|
|
||||||
</Text>
|
|
||||||
<HelpDisclosure title={<Trans>How does this work?</Trans>}>
|
<HelpDisclosure title={<Trans>How does this work?</Trans>}>
|
||||||
<Trans
|
<Trans
|
||||||
defaults="You can import a zip file that was previously exported from the JSON option in another instance. In {{ appName }}, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>."
|
defaults="You can import a zip file that was previously exported from the JSON option in another instance. In {{ appName }}, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>."
|
||||||
@@ -34,7 +21,16 @@ function ImportJSONDialog() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</HelpDisclosure>
|
</HelpDisclosure>
|
||||||
</Flex>
|
<DropToImport
|
||||||
|
onSubmit={dialogs.closeAllModals}
|
||||||
|
format={FileOperationFormat.JSON}
|
||||||
|
>
|
||||||
|
<Trans>
|
||||||
|
Drag and drop the zip file from the JSON export option in{" "}
|
||||||
|
{{ appName }}, or click to upload
|
||||||
|
</Trans>
|
||||||
|
</DropToImport>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
import { FileOperationFormat } from "@shared/types";
|
import { FileOperationFormat } from "@shared/types";
|
||||||
import Flex from "~/components/Flex";
|
|
||||||
import Text from "~/components/Text";
|
|
||||||
import env from "~/env";
|
import env from "~/env";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import DropToImport from "./DropToImport";
|
import DropToImport from "./DropToImport";
|
||||||
@@ -13,18 +11,7 @@ function ImportMarkdownDialog() {
|
|||||||
const appName = env.APP_NAME;
|
const appName = env.APP_NAME;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex column>
|
<>
|
||||||
<Text as="p" type="secondary">
|
|
||||||
<DropToImport
|
|
||||||
onSubmit={dialogs.closeAllModals}
|
|
||||||
format={FileOperationFormat.MarkdownZip}
|
|
||||||
>
|
|
||||||
<Trans>
|
|
||||||
Drag and drop the zip file from the Markdown export option in{" "}
|
|
||||||
{{ appName }}, or click to upload
|
|
||||||
</Trans>
|
|
||||||
</DropToImport>
|
|
||||||
</Text>
|
|
||||||
<HelpDisclosure title={<Trans>How does this work?</Trans>}>
|
<HelpDisclosure title={<Trans>How does this work?</Trans>}>
|
||||||
<Trans
|
<Trans
|
||||||
defaults="You can import a zip file that was previously exported from an Outline installation – collections, documents, and images will be imported. In Outline, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>."
|
defaults="You can import a zip file that was previously exported from an Outline installation – collections, documents, and images will be imported. In Outline, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>."
|
||||||
@@ -33,7 +20,16 @@ function ImportMarkdownDialog() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</HelpDisclosure>
|
</HelpDisclosure>
|
||||||
</Flex>
|
<DropToImport
|
||||||
|
onSubmit={dialogs.closeAllModals}
|
||||||
|
format={FileOperationFormat.MarkdownZip}
|
||||||
|
>
|
||||||
|
<Trans>
|
||||||
|
Drag and drop the zip file from the Markdown export option in{" "}
|
||||||
|
{{ appName }}, or click to upload
|
||||||
|
</Trans>
|
||||||
|
</DropToImport>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { FileOperationFormat } from "@shared/types";
|
import { FileOperationFormat } from "@shared/types";
|
||||||
import Flex from "~/components/Flex";
|
|
||||||
import Text from "~/components/Text";
|
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import DropToImport from "./DropToImport";
|
import DropToImport from "./DropToImport";
|
||||||
import HelpDisclosure from "./HelpDisclosure";
|
import HelpDisclosure from "./HelpDisclosure";
|
||||||
@@ -12,19 +10,7 @@ function ImportNotionDialog() {
|
|||||||
const { dialogs } = useStores();
|
const { dialogs } = useStores();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex column>
|
<>
|
||||||
<Text as="p" type="secondary">
|
|
||||||
<DropToImport
|
|
||||||
onSubmit={dialogs.closeAllModals}
|
|
||||||
format={FileOperationFormat.Notion}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
{t(
|
|
||||||
`Drag and drop the zip file from Notion's HTML export option, or click to upload`
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
</DropToImport>
|
|
||||||
</Text>
|
|
||||||
<HelpDisclosure title={<Trans>Where do I find the file?</Trans>}>
|
<HelpDisclosure title={<Trans>Where do I find the file?</Trans>}>
|
||||||
<Trans
|
<Trans
|
||||||
defaults="In Notion, click <em>Settings & Members</em> in the left sidebar and open Settings. Look for the Export section, and click <em>Export all workspace content</em>. Choose <em>HTML</em> as the format for the best data compatability."
|
defaults="In Notion, click <em>Settings & Members</em> in the left sidebar and open Settings. Look for the Export section, and click <em>Export all workspace content</em>. Choose <em>HTML</em> as the format for the best data compatability."
|
||||||
@@ -33,7 +19,17 @@ function ImportNotionDialog() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</HelpDisclosure>
|
</HelpDisclosure>
|
||||||
</Flex>
|
<DropToImport
|
||||||
|
onSubmit={dialogs.closeAllModals}
|
||||||
|
format={FileOperationFormat.Notion}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
{t(
|
||||||
|
`Drag and drop the zip file from Notion's HTML export option, or click to upload`
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</DropToImport>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -144,11 +144,13 @@ export default class CollectionsStore extends Store<Collection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
import = async (attachmentId: string, format?: string) => {
|
import = async (
|
||||||
|
attachmentId: string,
|
||||||
|
options: { format?: string; permission?: CollectionPermission | null }
|
||||||
|
) => {
|
||||||
await client.post("/collections.import", {
|
await client.post("/collections.import", {
|
||||||
type: "outline",
|
|
||||||
format,
|
|
||||||
attachmentId,
|
attachmentId,
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -53,9 +53,11 @@ async function collectionExporter({
|
|||||||
url: null,
|
url: null,
|
||||||
size: 0,
|
size: 0,
|
||||||
collectionId,
|
collectionId,
|
||||||
includeAttachments,
|
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: user.teamId,
|
teamId: user.teamId,
|
||||||
|
options: {
|
||||||
|
includeAttachments,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
transaction,
|
transaction,
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: async (queryInterface, Sequelize) => {
|
||||||
|
return queryInterface.addColumn("file_operations", "options", {
|
||||||
|
type: Sequelize.JSONB,
|
||||||
|
allowNull: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: async (queryInterface) => {
|
||||||
|
return queryInterface.removeColumn("file_operations", "options");
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
DataType,
|
DataType,
|
||||||
} from "sequelize-typescript";
|
} from "sequelize-typescript";
|
||||||
import {
|
import {
|
||||||
|
CollectionPermission,
|
||||||
FileOperationFormat,
|
FileOperationFormat,
|
||||||
FileOperationState,
|
FileOperationState,
|
||||||
FileOperationType,
|
FileOperationType,
|
||||||
@@ -25,6 +26,11 @@ import User from "./User";
|
|||||||
import ParanoidModel from "./base/ParanoidModel";
|
import ParanoidModel from "./base/ParanoidModel";
|
||||||
import Fix from "./decorators/Fix";
|
import Fix from "./decorators/Fix";
|
||||||
|
|
||||||
|
export type FileOperationOptions = {
|
||||||
|
includeAttachments?: boolean;
|
||||||
|
permission?: CollectionPermission | null;
|
||||||
|
};
|
||||||
|
|
||||||
@DefaultScope(() => ({
|
@DefaultScope(() => ({
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
@@ -66,8 +72,11 @@ class FileOperation extends ParanoidModel<
|
|||||||
@Column(DataType.BIGINT)
|
@Column(DataType.BIGINT)
|
||||||
size: number;
|
size: number;
|
||||||
|
|
||||||
@Column(DataType.BOOLEAN)
|
/**
|
||||||
includeAttachments: boolean;
|
* Additional configuration options for the file operation.
|
||||||
|
*/
|
||||||
|
@Column(DataType.JSON)
|
||||||
|
options: FileOperationOptions | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark the current file operation as expired and remove the file from storage.
|
* Mark the current file operation as expired and remove the file from storage.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default class ExportHTMLZipTask extends ExportDocumentTreeTask {
|
|||||||
zip,
|
zip,
|
||||||
collections,
|
collections,
|
||||||
FileOperationFormat.HTMLZip,
|
FileOperationFormat.HTMLZip,
|
||||||
fileOperation.includeAttachments
|
fileOperation.options?.includeAttachments ?? true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default class ExportJSONTask extends ExportTask {
|
|||||||
await this.addCollectionToArchive(
|
await this.addCollectionToArchive(
|
||||||
zip,
|
zip,
|
||||||
collection,
|
collection,
|
||||||
fileOperation.includeAttachments
|
fileOperation.options?.includeAttachments ?? true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default class ExportMarkdownZipTask extends ExportDocumentTreeTask {
|
|||||||
zip,
|
zip,
|
||||||
collections,
|
collections,
|
||||||
FileOperationFormat.MarkdownZip,
|
FileOperationFormat.MarkdownZip,
|
||||||
fileOperation.includeAttachments
|
fileOperation.options?.includeAttachments
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default abstract class ExportTask extends BaseTask<Props> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
fileOperation.includeAttachments &&
|
fileOperation.options?.includeAttachments &&
|
||||||
env.MAXIMUM_EXPORT_SIZE &&
|
env.MAXIMUM_EXPORT_SIZE &&
|
||||||
totalAttachmentsSize > env.MAXIMUM_EXPORT_SIZE
|
totalAttachmentsSize > env.MAXIMUM_EXPORT_SIZE
|
||||||
) {
|
) {
|
||||||
@@ -74,7 +74,7 @@ export default abstract class ExportTask extends BaseTask<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Logger.info("task", `ExportTask processing data for ${fileOperationId}`, {
|
Logger.info("task", `ExportTask processing data for ${fileOperationId}`, {
|
||||||
includeAttachments: fileOperation.includeAttachments,
|
options: fileOperation.options,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.updateFileOperation(fileOperation, {
|
await this.updateFileOperation(fileOperation, {
|
||||||
|
|||||||
@@ -391,7 +391,11 @@ export default abstract class ImportTask extends BaseTask<Props> {
|
|||||||
teamId: fileOperation.teamId,
|
teamId: fileOperation.teamId,
|
||||||
createdById: fileOperation.userId,
|
createdById: fileOperation.userId,
|
||||||
name,
|
name,
|
||||||
permission: item.permission ?? CollectionPermission.ReadWrite,
|
permission:
|
||||||
|
item.permission ??
|
||||||
|
fileOperation.options?.permission !== undefined
|
||||||
|
? fileOperation.options?.permission
|
||||||
|
: CollectionPermission.ReadWrite,
|
||||||
importId: fileOperation.id,
|
importId: fileOperation.id,
|
||||||
},
|
},
|
||||||
{ transaction }
|
{ transaction }
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ router.post(
|
|||||||
transaction(),
|
transaction(),
|
||||||
async (ctx: APIContext<T.CollectionsImportReq>) => {
|
async (ctx: APIContext<T.CollectionsImportReq>) => {
|
||||||
const { transaction } = ctx.state;
|
const { transaction } = ctx.state;
|
||||||
const { attachmentId, format } = ctx.input.body;
|
const { attachmentId, permission, format } = ctx.input.body;
|
||||||
|
|
||||||
const { user } = ctx.state.auth;
|
const { user } = ctx.state.auth;
|
||||||
authorize(user, "importCollection", user.team);
|
authorize(user, "importCollection", user.team);
|
||||||
@@ -179,6 +179,9 @@ router.post(
|
|||||||
key: attachment.key,
|
key: attachment.key,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: user.teamId,
|
teamId: user.teamId,
|
||||||
|
options: {
|
||||||
|
permission,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
transaction,
|
transaction,
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ export type CollectionsDocumentsReq = z.infer<
|
|||||||
|
|
||||||
export const CollectionsImportSchema = BaseSchema.extend({
|
export const CollectionsImportSchema = BaseSchema.extend({
|
||||||
body: z.object({
|
body: z.object({
|
||||||
|
permission: z
|
||||||
|
.nativeEnum(CollectionPermission)
|
||||||
|
.nullish()
|
||||||
|
.transform((val) => (isUndefined(val) ? null : val)),
|
||||||
attachmentId: z.string().uuid(),
|
attachmentId: z.string().uuid(),
|
||||||
format: z
|
format: z
|
||||||
.nativeEnum(FileOperationFormat)
|
.nativeEnum(FileOperationFormat)
|
||||||
|
|||||||
@@ -783,6 +783,8 @@
|
|||||||
"Please choose a single file to import": "Please choose a single file to import",
|
"Please choose a single file to import": "Please choose a single file to import",
|
||||||
"Your import is being processed, you can safely leave this page": "Your import is being processed, you can safely leave this page",
|
"Your import is being processed, you can safely leave this page": "Your import is being processed, you can safely leave this page",
|
||||||
"File not supported – please upload a valid ZIP file": "File not supported – please upload a valid ZIP file",
|
"File not supported – please upload a valid ZIP file": "File not supported – please upload a valid ZIP file",
|
||||||
|
"Set the default permission level for collections created from the import": "Set the default permission level for collections created from the import",
|
||||||
|
"Start import": "Start import",
|
||||||
"Processing": "Processing",
|
"Processing": "Processing",
|
||||||
"Expired": "Expired",
|
"Expired": "Expired",
|
||||||
"Completed": "Completed",
|
"Completed": "Completed",
|
||||||
@@ -795,14 +797,14 @@
|
|||||||
"Check server logs for more details.": "Check server logs for more details.",
|
"Check server logs for more details.": "Check server logs for more details.",
|
||||||
"{{userName}} requested": "{{userName}} requested",
|
"{{userName}} requested": "{{userName}} requested",
|
||||||
"Upload": "Upload",
|
"Upload": "Upload",
|
||||||
"Drag and drop the zip file from the JSON export option in {{appName}}, or click to upload": "Drag and drop the zip file from the JSON export option in {{appName}}, or click to upload",
|
|
||||||
"How does this work?": "How does this work?",
|
"How does this work?": "How does this work?",
|
||||||
"You can import a zip file that was previously exported from the JSON option in another instance. In {{ appName }}, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>.": "You can import a zip file that was previously exported from the JSON option in another instance. In {{ appName }}, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>.",
|
"You can import a zip file that was previously exported from the JSON option in another instance. In {{ appName }}, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>.": "You can import a zip file that was previously exported from the JSON option in another instance. In {{ appName }}, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>.",
|
||||||
"Drag and drop the zip file from the Markdown export option in {{appName}}, or click to upload": "Drag and drop the zip file from the Markdown export option in {{appName}}, or click to upload",
|
"Drag and drop the zip file from the JSON export option in {{appName}}, or click to upload": "Drag and drop the zip file from the JSON export option in {{appName}}, or click to upload",
|
||||||
"You can import a zip file that was previously exported from an Outline installation – collections, documents, and images will be imported. In Outline, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>.": "You can import a zip file that was previously exported from an Outline installation – collections, documents, and images will be imported. In Outline, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>.",
|
"You can import a zip file that was previously exported from an Outline installation – collections, documents, and images will be imported. In Outline, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>.": "You can import a zip file that was previously exported from an Outline installation – collections, documents, and images will be imported. In Outline, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>.",
|
||||||
"Drag and drop the zip file from Notion's HTML export option, or click to upload": "Drag and drop the zip file from Notion's HTML export option, or click to upload",
|
"Drag and drop the zip file from the Markdown export option in {{appName}}, or click to upload": "Drag and drop the zip file from the Markdown export option in {{appName}}, or click to upload",
|
||||||
"Where do I find the file?": "Where do I find the file?",
|
"Where do I find the file?": "Where do I find the file?",
|
||||||
"In Notion, click <em>Settings & Members</em> in the left sidebar and open Settings. Look for the Export section, and click <em>Export all workspace content</em>. Choose <em>HTML</em> as the format for the best data compatability.": "In Notion, click <em>Settings & Members</em> in the left sidebar and open Settings. Look for the Export section, and click <em>Export all workspace content</em>. Choose <em>HTML</em> as the format for the best data compatability.",
|
"In Notion, click <em>Settings & Members</em> in the left sidebar and open Settings. Look for the Export section, and click <em>Export all workspace content</em>. Choose <em>HTML</em> as the format for the best data compatability.": "In Notion, click <em>Settings & Members</em> in the left sidebar and open Settings. Look for the Export section, and click <em>Export all workspace content</em>. Choose <em>HTML</em> as the format for the best data compatability.",
|
||||||
|
"Drag and drop the zip file from Notion's HTML export option, or click to upload": "Drag and drop the zip file from Notion's HTML export option, or click to upload",
|
||||||
"Last active": "Last active",
|
"Last active": "Last active",
|
||||||
"Guest": "Guest",
|
"Guest": "Guest",
|
||||||
"Shared": "Shared",
|
"Shared": "Shared",
|
||||||
|
|||||||
Reference in New Issue
Block a user