diff --git a/packages/commonwealth/client/scripts/utils/downloadDataAsFile.ts b/packages/commonwealth/client/scripts/utils/downloadDataAsFile.ts new file mode 100644 index 00000000000..2e2ca309f04 --- /dev/null +++ b/packages/commonwealth/client/scripts/utils/downloadDataAsFile.ts @@ -0,0 +1,23 @@ +/** + * Save the content locally and use the `filename` as the suggested name for the + * file. + */ +export function downloadDataAsFile( + content: string, + type: string, + filename: string, +) { + const blob = new Blob([content], { type }); + + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = filename; + + document.body.appendChild(link); + + link.click(); + + document.body.removeChild(link); + + URL.revokeObjectURL(link.href); +} diff --git a/packages/commonwealth/client/scripts/views/components/component_kit/cw_icons/cw_icon_lookup.ts b/packages/commonwealth/client/scripts/views/components/component_kit/cw_icons/cw_icon_lookup.ts index 54d7b2c0796..5eb9137b57a 100644 --- a/packages/commonwealth/client/scripts/views/components/component_kit/cw_icons/cw_icon_lookup.ts +++ b/packages/commonwealth/client/scripts/views/components/component_kit/cw_icons/cw_icon_lookup.ts @@ -30,6 +30,7 @@ import { Compass, Copy, DotsThreeVertical, + Download, Export, Eye, Flag, @@ -240,6 +241,7 @@ export const iconLookup = { website: Icons.CWWebsite, write: Icons.CWWrite, members: Icons.CWMembers, + download: withPhosphorIcon(Download), }; export const customIconLookup = { diff --git a/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/AdminActions/AdminActions.tsx b/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/AdminActions/AdminActions.tsx index e27142782e0..764b5c04960 100644 --- a/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/AdminActions/AdminActions.tsx +++ b/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/AdminActions/AdminActions.tsx @@ -26,6 +26,7 @@ import './AdminActions.scss'; export type AdminActionsProps = { thread: Thread; + canUpdateThread?: boolean; onDelete?: () => any; onSpamToggle?: (thread: Thread) => any; onLockToggle?: (isLocked: boolean) => any; @@ -36,6 +37,7 @@ export type AdminActionsProps = { onEditStart?: () => any; onEditConfirm?: () => any; onEditCancel?: () => any; + onDownloadMarkdown?: () => void; hasPendingEdits?: boolean; editingDisabled?: boolean; }; @@ -54,6 +56,8 @@ export const AdminActions = ({ onEditConfirm, hasPendingEdits, editingDisabled, + onDownloadMarkdown, + canUpdateThread, }: AdminActionsProps) => { const navigate = useCommonNavigate(); const [isEditCollaboratorsModalOpen, setIsEditCollaboratorsModalOpen] = @@ -296,7 +300,8 @@ export const AdminActions = ({ onDownloadMarkdown?.(), + label: 'Download as Markdown', + iconLeft: 'download' as const, + iconLeftWeight: 'bold' as const, + }, + ], + ...(canUpdateThread && (isThreadAuthor || hasAdminPermissions) ? [ ...(app.chain?.meta.snapshot.length ? [ diff --git a/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/ThreadOptions.tsx b/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/ThreadOptions.tsx index 3eaf4799f5f..697384a662c 100644 --- a/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/ThreadOptions.tsx +++ b/packages/commonwealth/client/scripts/views/pages/discussions/ThreadCard/ThreadOptions/ThreadOptions.tsx @@ -12,6 +12,7 @@ import React, { import { useCreateThreadSubscriptionMutation } from 'state/api/trpc/subscription/useCreateThreadSubscriptionMutation'; import { useDeleteThreadSubscriptionMutation } from 'state/api/trpc/subscription/useDeleteThreadSubscriptionMutation'; import Permissions from 'utils/Permissions'; +import { downloadDataAsFile } from 'utils/downloadDataAsFile'; import { SharePopover } from 'views/components/SharePopover'; import { ViewUpvotesDrawerTrigger } from 'views/components/UpvoteDrawer'; import { CWThreadAction } from 'views/components/component_kit/new_designs/cw_thread_action'; @@ -93,6 +94,10 @@ export const ThreadOptions = ({ ); }, [isSubscribed, thread]); + const handleDownloadMarkdown = () => { + downloadDataAsFile(thread.plaintext, 'text/markdown', thread.title + '.md'); + }; + const createThreadSubscriptionMutation = useCreateThreadSubscriptionMutation(); const deleteThreadSubscriptionMutation = @@ -226,8 +231,9 @@ export const ThreadOptions = ({ /> )} - {canUpdateThread && thread && ( + {thread && ( diff --git a/packages/commonwealth/client/scripts/views/pages/view_proposal/JSONDisplay.tsx b/packages/commonwealth/client/scripts/views/pages/view_proposal/JSONDisplay.tsx index 255f4be9bc4..20e977e9364 100644 --- a/packages/commonwealth/client/scripts/views/pages/view_proposal/JSONDisplay.tsx +++ b/packages/commonwealth/client/scripts/views/pages/view_proposal/JSONDisplay.tsx @@ -3,6 +3,7 @@ import 'components/proposals/json_display.scss'; import { CoinObject } from 'controllers/chain/cosmos/types'; import React from 'react'; import app from 'state'; +import { downloadDataAsFile } from 'utils/downloadDataAsFile'; import { CWDivider } from '../../components/component_kit/cw_divider'; import { CWText } from '../../components/component_kit/cw_text'; import { CWButton } from '../../components/component_kit/new_designs/CWButton'; @@ -26,20 +27,7 @@ export const JSONDisplay = ({ data, title }: JSONDisplayProps) => { const handleExport = () => { const dataTitle = data.title || 'Proposal'; const proposalDetails = data.details || ''; - - const blob = new Blob([proposalDetails], { type: 'text/markdown' }); - - const link = document.createElement('a'); - link.href = URL.createObjectURL(blob); - link.download = `${dataTitle}.md`; - - document.body.appendChild(link); - - link.click(); - - document.body.removeChild(link); - - URL.revokeObjectURL(link.href); + downloadDataAsFile(proposalDetails, 'text/markdown', `${dataTitle}.md`); }; return (