@@ -29,6 +29,7 @@ import DocumentPermanentDelete from "~/scenes/DocumentPermanentDelete";
|
||||
import DocumentTemplatizeDialog from "~/components/DocumentTemplatizeDialog";
|
||||
import { createAction } from "~/actions";
|
||||
import { DocumentSection } from "~/actions/sections";
|
||||
import env from "~/env";
|
||||
import history from "~/utils/history";
|
||||
import {
|
||||
documentInsightsUrl,
|
||||
@@ -206,6 +207,33 @@ export const downloadDocumentAsHTML = createAction({
|
||||
},
|
||||
});
|
||||
|
||||
export const downloadDocumentAsPDF = createAction({
|
||||
name: ({ t }) => t("PDF"),
|
||||
section: DocumentSection,
|
||||
keywords: "export",
|
||||
icon: <DownloadIcon />,
|
||||
iconInContextMenu: false,
|
||||
visible: ({ activeDocumentId, stores }) =>
|
||||
!!activeDocumentId &&
|
||||
stores.policies.abilities(activeDocumentId).download &&
|
||||
env.PDF_EXPORT_ENABLED,
|
||||
perform: ({ activeDocumentId, t, stores }) => {
|
||||
if (!activeDocumentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = stores.toasts.showToast(`${t("Exporting")}…`, {
|
||||
type: "loading",
|
||||
timeout: 30 * 1000,
|
||||
});
|
||||
|
||||
const document = stores.documents.get(activeDocumentId);
|
||||
document
|
||||
?.download("application/pdf")
|
||||
.finally(() => id && stores.toasts.hideToast(id));
|
||||
},
|
||||
});
|
||||
|
||||
export const downloadDocumentAsMarkdown = createAction({
|
||||
name: ({ t }) => t("Markdown"),
|
||||
section: DocumentSection,
|
||||
@@ -230,7 +258,11 @@ export const downloadDocument = createAction({
|
||||
section: DocumentSection,
|
||||
icon: <DownloadIcon />,
|
||||
keywords: "export",
|
||||
children: [downloadDocumentAsHTML, downloadDocumentAsMarkdown],
|
||||
children: [
|
||||
downloadDocumentAsHTML,
|
||||
downloadDocumentAsPDF,
|
||||
downloadDocumentAsMarkdown,
|
||||
],
|
||||
});
|
||||
|
||||
export const duplicateDocument = createAction({
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
export default function Spinner(props: React.HTMLAttributes<HTMLOrSVGElement>) {
|
||||
type Props = React.HTMLAttributes<HTMLOrSVGElement> & {
|
||||
color?: string;
|
||||
};
|
||||
|
||||
export default function Spinner({ color, ...props }: Props) {
|
||||
return (
|
||||
<SVG
|
||||
width="16px"
|
||||
@@ -11,6 +15,7 @@ export default function Spinner(props: React.HTMLAttributes<HTMLOrSVGElement>) {
|
||||
{...props}
|
||||
>
|
||||
<Circle
|
||||
$color={color}
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
@@ -36,7 +41,7 @@ const SVG = styled.svg`
|
||||
margin: 4px;
|
||||
`;
|
||||
|
||||
const Circle = styled.circle`
|
||||
const Circle = styled.circle<{ $color?: string }>`
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dashoffset: 47;
|
||||
@@ -51,7 +56,7 @@ const Circle = styled.circle`
|
||||
}
|
||||
}
|
||||
|
||||
stroke: ${(props) => props.theme.textSecondary};
|
||||
stroke: ${(props) => props.$color || props.theme.textSecondary};
|
||||
stroke-dasharray: 46;
|
||||
stroke-dashoffset: 0;
|
||||
transform-origin: center;
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { fadeAndScaleIn, pulse } from "~/styles/animations";
|
||||
import { Toast as TToast } from "~/types";
|
||||
import Spinner from "./Spinner";
|
||||
|
||||
type Props = {
|
||||
onRequestClose: () => void;
|
||||
@@ -56,6 +57,7 @@ function Toast({ closeAfterMs = 3000, onRequestClose, toast }: Props) {
|
||||
onMouseLeave={handleResume}
|
||||
>
|
||||
<Container onClick={action ? undefined : onRequestClose}>
|
||||
{type === "loading" && <Spinner color="currentColor" />}
|
||||
{type === "info" && <InfoIcon color="currentColor" />}
|
||||
{type === "success" && <CheckboxIcon checked color="currentColor" />}
|
||||
{type === "warning" ||
|
||||
|
||||
@@ -419,7 +419,9 @@ export default class Document extends ParanoidModel {
|
||||
};
|
||||
}
|
||||
|
||||
download = async (contentType: "text/html" | "text/markdown") => {
|
||||
download = async (
|
||||
contentType: "text/html" | "text/markdown" | "application/pdf"
|
||||
) => {
|
||||
await client.post(
|
||||
`/documents.export`,
|
||||
{
|
||||
|
||||
@@ -124,7 +124,7 @@ export type Toast = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
message: string;
|
||||
type: "warning" | "error" | "info" | "success";
|
||||
type: "warning" | "error" | "info" | "success" | "loading";
|
||||
timeout?: number;
|
||||
reoccurring?: number;
|
||||
action?: {
|
||||
@@ -176,7 +176,7 @@ export type SearchResult = {
|
||||
};
|
||||
|
||||
export type ToastOptions = {
|
||||
type: "warning" | "error" | "info" | "success";
|
||||
type: "warning" | "error" | "info" | "success" | "loading";
|
||||
timeout?: number;
|
||||
action?: {
|
||||
text: string;
|
||||
|
||||
Reference in New Issue
Block a user