You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-03 23:50:33 +02:00
209 lines
5.8 KiB
TypeScript
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);
|