1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-03 23:50:33 +02:00
Files
joplin/packages/app-mobile/components/screens/ShareNoteDialog.tsx

209 lines
5.8 KiB
TypeScript

import * as React from 'react';
import { Button, Divider, Text } from 'react-native-paper';
import { View, StyleSheet, ScrollView, Linking } from 'react-native';
import Note from '@joplin/lib/models/Note';
import { NoteEntity } from '@joplin/lib/services/database/types';
import { StateShare } from '@joplin/lib/services/share/reducer';
import ShareService from '@joplin/lib/services/share/ShareService';
import { useCallback, useEffect, useMemo, useState } from 'react';
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
import Clipboard from '@react-native-clipboard/clipboard';
import useOnShareLinkClick from '@joplin/lib/components/shared/ShareNoteDialog/useOnShareLinkClick';
import onUnshareNoteClick from '@joplin/lib/components/shared/ShareNoteDialog/onUnshareNoteClick';
import useShareStatusMessage from '@joplin/lib/components/shared/ShareNoteDialog/useShareStatusMessage';
import useEncryptionWarningMessage from '@joplin/lib/components/shared/ShareNoteDialog/useEncryptionWarningMessage';
import { SharingStatus } from '@joplin/lib/components/shared/ShareNoteDialog/types';
import { AppState } from '../../utils/types';
import { connect } from 'react-redux';
import DismissibleDialog, { DialogSize } from '../DismissibleDialog';
import { _, _n } from '@joplin/lib/locale';
import { LinkButton, PrimaryButton } from '../buttons';
import { themeStyle } from '../global-style';
interface Props {
themeId: number;
noteId: string;
visible: boolean;
onClose: ()=> void;
shares: StateShare[];
}
const useStyles = (themeId: number) => {
return useMemo(() => {
const theme = themeStyle(themeId);
return StyleSheet.create({
root: {
flexGrow: 1,
},
scrollingRegion: {
flexGrow: 1,
},
noteItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: 8,
paddingBottom: 8,
minHeight: 32,
},
noteTitle: {
fontSize: theme.fontSize,
},
});
}, [themeId]);
};
interface UnpublishProps {
note: NoteEntity;
onUnpublishStart: ()=> void;
}
const UnpublishButton: React.FC<UnpublishProps> = ({ note, onUnpublishStart }) => {
const [unpublishing, setUnpublishing] = useState(false);
const onPress = useCallback(async () => {
onUnpublishStart();
try {
setUnpublishing(true);
await onUnshareNoteClick({ noteId: note.id });
} finally {
setUnpublishing(false);
}
}, [note, onUnpublishStart]);
return <Button
accessibilityLabel={_('Unpublish "%s"', note.title)}
loading={unpublishing}
disabled={unpublishing}
icon='share-off'
onPress={onPress}
>{_('Unpublish')}</Button>;
};
const ShareNoteDialogContent: React.FC<Props> = ({
themeId, noteId, shares,
}) => {
const [notes, setNotes] = useState<NoteEntity[]>([]);
const recursiveShare = false;
const [sharesState, setSharesState] = useState<SharingStatus>(SharingStatus.Unknown);
const [shareLinks, setShareLinks] = useState<string[]>([]);
const noteCount = notes.length;
useEffect(() => {
void ShareService.instance().refreshShares();
}, []);
useAsyncEffect(async (event) => {
const note = await Note.load(noteId);
if (event.cancelled) return;
setNotes([note]);
}, [noteId]);
const onCopyLinks = useCallback(async (links: string[]) => {
setShareLinks(links);
const linkText = links.join('\n');
Clipboard.setString(linkText);
}, []);
const onUnpublishStart = useCallback(() => {
setShareLinks([]);
}, []);
const shareLinkButton_click = useOnShareLinkClick({
setSharesState,
onShareUrlsReady: onCopyLinks,
notes,
recursiveShare,
});
const styles = useStyles(themeId);
const renderNote = (note: NoteEntity) => {
const unshareButton = shares.find(s => s.note_id === note.id) ? (
<UnpublishButton note={note} onUnpublishStart={onUnpublishStart}/>
) : null;
return (
<View key={note.id} style={styles.noteItem}>
<Text style={styles.noteTitle}>{note.title}</Text>{unshareButton}
</View>
);
};
const renderNoteList = (notes: NoteEntity[]) => {
const noteComps = [];
for (const note of notes) {
noteComps.push(renderNote(note));
}
return <View>{noteComps}</View>;
};
const statusMessage = useShareStatusMessage({
sharesState, noteCount,
});
const encryptionMessage = useEncryptionWarningMessage();
const renderEncryptionWarningMessage = () => {
if (!encryptionMessage) return null;
return <>
<Text>{encryptionMessage}</Text>
<Divider/>
</>;
};
const renderLinks = () => {
if (shareLinks.length === 0) return null;
return <>
<Divider/>
<Text variant='titleMedium' accessibilityRole='header'>{_('Links')}</Text>
{shareLinks.map((link, index) => {
return <LinkButton
onPress={() => Linking.openURL(link)}
key={`link-${index}`}
>{link}</LinkButton>;
})}
</>;
};
const copyButtonLoading = [SharingStatus.Creating, SharingStatus.Synchronizing].includes(sharesState);
const copyLinkButton = <PrimaryButton
disabled={copyButtonLoading}
loading={copyButtonLoading}
onPress={shareLinkButton_click}
>{
_n('Copy Shareable Link', 'Copy Shareable Links', noteCount)
}</PrimaryButton>;
return <View style={styles.root}>
<ScrollView style={styles.scrollingRegion}>
{renderEncryptionWarningMessage()}
<Divider/>
{renderNoteList(notes)}
{renderLinks()}
</ScrollView>
<Text aria-live='polite'>{statusMessage}</Text>
{copyLinkButton}
</View>;
};
const ShareNoteDialog: React.FC<Props> = props => {
return <DismissibleDialog
themeId={props.themeId}
visible={props.visible}
onDismiss={props.onClose}
size={DialogSize.Small}
heading={_('Publish Note')}
>
{props.visible ? <ShareNoteDialogContent {...props}/> : null}
</DismissibleDialog>;
};
const mapStateToProps = (state: AppState) => {
return {
themeId: state.settings.theme,
shares: state.shareService.shares.filter(s => !!s.note_id),
};
};
export default connect(mapStateToProps)(ShareNoteDialog);