import * as React from 'react'; import { useState, useEffect, useCallback, useMemo } from 'react'; import JoplinServerApi from '@joplin/lib/JoplinServerApi'; import { _, _n } from '@joplin/lib/locale'; import Note from '@joplin/lib/models/Note'; import DialogButtonRow from './DialogButtonRow'; import { themeStyle, buildStyle } from '@joplin/lib/theme'; import { reg } from '@joplin/lib/registry'; import Dialog from './Dialog'; import DialogTitle from './DialogTitle'; import ShareService from '@joplin/lib/services/share/ShareService'; import { StateShare } from '@joplin/lib/services/share/reducer'; import { NoteEntity } from '@joplin/lib/services/database/types'; import Button from './Button/Button'; import { connect } from 'react-redux'; import { AppState } from '../app.reducer'; import { getEncryptionEnabled } from '@joplin/lib/services/synchronizer/syncInfoUtils'; import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; const { clipboard } = require('electron'); interface Props { themeId: number; noteIds: string[]; // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied onClose: Function; shares: StateShare[]; syncTargetId: number; } function styles_(props: Props) { return buildStyle('ShareNoteDialog', props.themeId, (theme: any) => { return { root: { minWidth: 500, }, noteList: { marginBottom: 10, }, note: { flex: 1, flexDirection: 'row', display: 'flex', alignItems: 'center', border: '1px solid', borderColor: theme.dividerColor, padding: '0.5em', marginBottom: 5, }, noteTitle: { ...theme.textStyle, flex: 1, display: 'flex', color: theme.color, }, noteRemoveButton: { background: 'none', border: 'none', }, noteRemoveButtonIcon: { color: theme.color, fontSize: '1.4em', }, copyShareLinkButton: { ...theme.buttonStyle, marginBottom: 10, }, }; }); } export function ShareNoteDialog(props: Props) { const [notes, setNotes] = useState([]); const [recursiveShare, setRecursiveShare] = useState(false); const [sharesState, setSharesState] = useState('unknown'); const syncTargetInfo = useMemo(() => SyncTargetRegistry.infoById(props.syncTargetId), [props.syncTargetId]); const noteCount = notes.length; const theme = themeStyle(props.themeId); const styles = styles_(props); useEffect(() => { void ShareService.instance().refreshShares(); }, []); useEffect(() => { async function fetchNotes() { const result = []; for (const noteId of props.noteIds) { result.push(await Note.load(noteId)); } setNotes(result); } void fetchNotes(); }, [props.noteIds]); const buttonRow_click = () => { props.onClose(); }; const copyLinksToClipboard = (shares: StateShare[]) => { const links = []; for (const share of shares) links.push(ShareService.instance().shareUrl(ShareService.instance().userId, share)); clipboard.writeText(links.join('\n')); }; const shareLinkButton_click = useCallback(async () => { const service = ShareService.instance(); let hasSynced = false; let tryToSync = false; while (true) { try { if (tryToSync) { setSharesState('synchronizing'); await reg.waitForSyncFinishedThenSync(); tryToSync = false; hasSynced = true; } setSharesState('creating'); const newShares: StateShare[] = []; for (const note of notes) { const share = await service.shareNote(note.id, recursiveShare); newShares.push(share); } setSharesState('synchronizing'); await reg.waitForSyncFinishedThenSync(); setSharesState('creating'); copyLinksToClipboard(newShares); setSharesState('created'); await ShareService.instance().refreshShares(); } catch (error) { if (error.code === 404 && !hasSynced) { reg.logger().info('ShareNoteDialog: Note does not exist on server - trying to sync it.', error); tryToSync = true; continue; } reg.logger().error('ShareNoteDialog: Cannot publish note:', error); setSharesState('idle'); alert(JoplinServerApi.connectionErrorMessage(error)); } break; } }, [recursiveShare, notes]); const unshareNoteButton_click = async (event: any) => { await ShareService.instance().unshareNote(event.noteId); await ShareService.instance().refreshShares(); }; const renderNote = (note: NoteEntity) => { const unshareButton = !props.shares.find(s => s.note_id === note.id) ? null : (
{statusMessage(sharesState)}
{renderEncryptionWarningMessage()} ); }; return ( ); } const mapStateToProps = (state: AppState) => { return { shares: state.shareService.shares.filter(s => !!s.note_id), syncTargetId: state.settings['sync.target'], }; }; export default connect(mapStateToProps)(ShareNoteDialog);