11import { useState } from "preact/hooks" ;
22import { t } from "../../services/i18n" ;
33import tree from "../../services/tree" ;
4+ import { copyTextWithToast } from "../../services/clipboard_ext.js" ;
45import Button from "../react/Button" ;
56import FormRadioGroup from "../react/FormRadioGroup" ;
7+ import FormToggle from "../react/FormToggle" ;
68import Modal from "../react/Modal" ;
79import "./export.css" ;
810import ws from "../../services/ws" ;
@@ -21,10 +23,12 @@ interface ExportDialogProps {
2123export default function ExportDialog ( ) {
2224 const [ opts , setOpts ] = useState < ExportDialogProps > ( ) ;
2325 const [ exportType , setExportType ] = useState < string > ( opts ?. defaultType ?? "subtree" ) ;
26+ const [ exportToClipboard , setExportToClipboard ] = useState ( false ) ;
2427 const [ subtreeFormat , setSubtreeFormat ] = useState ( "html" ) ;
2528 const [ singleFormat , setSingleFormat ] = useState ( "html" ) ;
2629 const [ opmlVersion , setOpmlVersion ] = useState ( "2.0" ) ;
2730 const [ shown , setShown ] = useState ( false ) ;
31+ const [ exporting , setExporting ] = useState ( false ) ;
2832
2933 useTriliumEvent ( "showExportDialog" , async ( { notePath, defaultType } ) => {
3034 const { noteId, parentNoteId } = tree . getNoteIdAndParentIdFromUrl ( notePath ) ;
@@ -47,18 +51,20 @@ export default function ExportDialog() {
4751 className = "export-dialog"
4852 title = { `${ t ( "export.export_note_title" ) } ${ opts ?. noteTitle ?? "" } ` }
4953 size = "lg"
50- onSubmit = { ( ) => {
54+ onSubmit = { async ( ) => {
5155 if ( ! opts || ! opts . branchId ) {
5256 return ;
5357 }
5458
5559 const format = ( exportType === "subtree" ? subtreeFormat : singleFormat ) ;
5660 const version = ( format === "opml" ? opmlVersion : "1.0" ) ;
57- exportBranch ( opts . branchId , exportType , format , version ) ;
61+ setExporting ( true ) ;
62+ await exportBranch ( opts . branchId , exportType , format , version , exportToClipboard ) ;
63+ setExporting ( false ) ;
5864 setShown ( false ) ;
5965 } }
6066 onHidden = { ( ) => setShown ( false ) }
61- footer = { < Button className = "export-button" text = { t ( "export.export" ) } primary /> }
67+ footer = { < Button className = "export-button" text = { t ( "export.export" ) } primary disabled = { exporting } /> }
6268 show = { shown }
6369 >
6470
@@ -118,17 +124,50 @@ export default function ExportDialog() {
118124 { value : "markdown" , label : t ( "export.format_markdown" ) }
119125 ] }
120126 />
127+
128+ < FormToggle
129+ switchOnName = { t ( "export.export_to_clipboard" ) } switchOnTooltip = { t ( "export.export_to_clipboard_on_tooltip" ) }
130+ switchOffName = { t ( "export.export_to_clipboard" ) } switchOffTooltip = { t ( "export.export_to_clipboard_off_tooltip" ) }
131+ currentValue = { exportToClipboard } onChange = { setExportToClipboard }
132+ />
121133 </ div >
122134 }
123135
124136 </ Modal >
125137 ) ;
126138}
127139
128- function exportBranch ( branchId : string , type : string , format : string , version : string ) {
140+ async function exportBranch ( branchId : string , type : string , format : string , version : string , exportToClipboard : boolean ) {
129141 const taskId = utils . randomString ( 10 ) ;
130142 const url = open . getUrlForDownload ( `api/branches/${ branchId } /export/${ type } /${ format } /${ version } /${ taskId } ` ) ;
131- open . download ( url ) ;
143+ if ( type === "single" && exportToClipboard ) {
144+ await exportSingleToClipboard ( url ) ;
145+ } else {
146+ open . download ( url ) ;
147+ }
148+ }
149+
150+ async function exportSingleToClipboard ( url : string ) {
151+ try {
152+ const res = await fetch ( url ) ;
153+ if ( ! res . ok ) {
154+ throw new Error ( `${ res . status } ${ res . statusText } ` ) ;
155+ }
156+ const blob = await res . blob ( ) ;
157+
158+ // Try reading as text (HTML/Markdown are text); if that fails, fall back to ArrayBuffer->UTF-8
159+ let text : string ;
160+ try {
161+ text = await blob . text ( ) ;
162+ } catch {
163+ const ab = await blob . arrayBuffer ( ) ;
164+ text = new TextDecoder ( "utf-8" ) . decode ( new Uint8Array ( ab ) ) ;
165+ }
166+
167+ await copyTextWithToast ( text ) ;
168+ } catch ( error ) {
169+ console . error ( "Failed to copy exported note to clipboard:" , error ) ;
170+ }
132171}
133172
134173ws . subscribeToMessages ( async ( message ) => {
0 commit comments