1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00
joplin/packages/app-desktop/gui/ShareNoteDialog.tsx
2021-01-29 18:45:11 +00:00

226 lines
6.4 KiB
TypeScript

import * as React from 'react';
import { useState, useEffect } from 'react';
import JoplinServerApi from '@joplin/lib/JoplinServerApi';
import { _, _n } from '@joplin/lib/locale';
import Note from '@joplin/lib/models/Note';
import Setting from '@joplin/lib/models/Setting';
import BaseItem from '@joplin/lib/models/BaseItem';
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
const { themeStyle, buildStyle } = require('@joplin/lib/theme');
const DialogButtonRow = require('./DialogButtonRow.min');
import { reg } from '@joplin/lib/registry';
const { clipboard } = require('electron');
interface ShareNoteDialogProps {
themeId: number;
noteIds: Array<string>;
onClose: Function;
}
interface SharesMap {
[key: string]: any;
}
function styles_(props: ShareNoteDialogProps) {
return buildStyle('ShareNoteDialog', props.themeId, (theme: any) => {
return {
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 default function ShareNoteDialog(props: ShareNoteDialogProps) {
console.info('Render ShareNoteDialog');
const [notes, setNotes] = useState<any[]>([]);
const [sharesState, setSharesState] = useState<string>('unknown');
const [shares, setShares] = useState<SharesMap>({});
const noteCount = notes.length;
const theme = themeStyle(props.themeId);
const styles = styles_(props);
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 fileApi = async () => {
const syncTarget = reg.syncTarget() as SyncTargetJoplinServer;
return syncTarget.fileApi();
};
const joplinServerApi = async (): Promise<JoplinServerApi> => {
return (await fileApi()).driver().api();
};
const buttonRow_click = () => {
props.onClose();
};
const copyLinksToClipboard = (api: JoplinServerApi, shares: SharesMap) => {
const links = [];
for (const n in shares) links.push(api.shareUrl(shares[n]));
clipboard.writeText(links.join('\n'));
};
const shareLinkButton_click = async () => {
let hasSynced = false;
let tryToSync = false;
while (true) {
try {
if (tryToSync) {
setSharesState('synchronizing');
await reg.waitForSyncFinishedThenSync();
tryToSync = false;
hasSynced = true;
}
setSharesState('creating');
const api = await joplinServerApi();
const newShares = Object.assign({}, shares);
let sharedStatusChanged = false;
for (const note of notes) {
const fullPath = (await fileApi()).fullPath(BaseItem.systemPath(note.id));
const share = await api.shareFile(fullPath);
newShares[note.id] = share;
const changed = await BaseItem.updateShareStatus(note, true);
if (changed) sharedStatusChanged = true;
}
setShares(newShares);
if (sharedStatusChanged) {
setSharesState('synchronizing');
await reg.waitForSyncFinishedThenSync();
setSharesState('creating');
}
copyLinksToClipboard(api, newShares);
setSharesState('created');
} 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 share note:', error);
setSharesState('idle');
alert(JoplinServerApi.connectionErrorMessage(error));
}
break;
}
};
const removeNoteButton_click = (event: any) => {
const newNotes = [];
for (let i = 0; i < notes.length; i++) {
const n = notes[i];
if (n.id === event.noteId) continue;
newNotes.push(n);
}
setNotes(newNotes);
};
const renderNote = (note: any) => {
const removeButton = notes.length <= 1 ? null : (
<button onClick={() => removeNoteButton_click({ noteId: note.id })} style={styles.noteRemoveButton}>
<i style={styles.noteRemoveButtonIcon} className={'fa fa-times'}></i>
</button>
);
return (
<div key={note.id} style={styles.note}>
<span style={styles.noteTitle}>{note.title}</span>{removeButton}
</div>
);
};
const renderNoteList = (notes: any) => {
const noteComps = [];
for (const noteId of Object.keys(notes)) {
noteComps.push(renderNote(notes[noteId]));
}
return <div style={styles.noteList}>{noteComps}</div>;
};
const statusMessage = (sharesState: string): string => {
if (sharesState === 'synchronizing') return _('Synchronising...');
if (sharesState === 'creating') return _n('Generating link...', 'Generating links...', noteCount);
if (sharesState === 'created') return _n('Link has been copied to clipboard!', 'Links have been copied to clipboard!', noteCount);
return '';
};
function renderEncryptionWarningMessage() {
if (!Setting.value('encryption.enabled')) return null;
return <div style={theme.textStyle}>{_('Note: When a note is shared, it will no longer be encrypted on the server.')}<hr/></div>;
}
function renderBetaWarningMessage() {
return <div style={theme.textStyle}>{'Sharing notes via Joplin Server is a Beta feature and the API might change later on. What it means is that if you share a note, the link might become invalid after an upgrade, and you will have to share it again.'}</div>;
}
const rootStyle = Object.assign({}, theme.dialogBox);
rootStyle.width = '50%';
return (
<div style={theme.dialogModalLayer}>
<div style={rootStyle}>
<div style={theme.dialogTitle}>{_('Share Notes')}</div>
{renderNoteList(notes)}
<button disabled={['creating', 'synchronizing'].indexOf(sharesState) >= 0} style={styles.copyShareLinkButton} onClick={shareLinkButton_click}>{_n('Copy Shareable Link', 'Copy Shareable Links', noteCount)}</button>
<div style={theme.textStyle}>{statusMessage(sharesState)}</div>
{renderEncryptionWarningMessage()}
{renderBetaWarningMessage()}
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
</div>
</div>
);
}