1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Chore: Moved share invitation response logic to separate file (Desktop)

This commit is contained in:
Laurent Cozic 2021-10-15 16:16:02 +01:00
parent a5f2fd8982
commit 8a7fa78c54
6 changed files with 87 additions and 38 deletions

View File

@ -732,6 +732,9 @@ packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
packages/app-desktop/services/share/invitationRespond.d.ts
packages/app-desktop/services/share/invitationRespond.js
packages/app-desktop/services/share/invitationRespond.js.map
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.d.ts packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.d.ts
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js.map packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js.map

3
.gitignore vendored
View File

@ -715,6 +715,9 @@ packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
packages/app-desktop/services/share/invitationRespond.d.ts
packages/app-desktop/services/share/invitationRespond.js
packages/app-desktop/services/share/invitationRespond.js.map
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.d.ts packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.d.ts
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js.map packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js.map

View File

@ -32,21 +32,17 @@ import removeItem from '../ResizableLayout/utils/removeItem';
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService'; import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import ShareFolderDialog from '../ShareFolderDialog/ShareFolderDialog'; import ShareFolderDialog from '../ShareFolderDialog/ShareFolderDialog';
import { ShareInvitation } from '@joplin/lib/services/share/reducer'; import { ShareInvitation } from '@joplin/lib/services/share/reducer';
import ShareService from '@joplin/lib/services/share/ShareService';
import { reg } from '@joplin/lib/registry';
import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems'; import removeKeylessItems from '../ResizableLayout/utils/removeKeylessItems';
import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils'; import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils'; import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
import commands from './commands/index'; import commands from './commands/index';
import Logger from '@joplin/lib/Logger'; import invitationRespond from '../../services/share/invitationRespond';
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { PromptDialog } = require('../PromptDialog.min.js'); const { PromptDialog } = require('../PromptDialog.min.js');
const NotePropertiesDialog = require('../NotePropertiesDialog.min.js'); const NotePropertiesDialog = require('../NotePropertiesDialog.min.js');
const PluginManager = require('@joplin/lib/services/PluginManager'); const PluginManager = require('@joplin/lib/services/PluginManager');
const ipcRenderer = require('electron').ipcRenderer; const ipcRenderer = require('electron').ipcRenderer;
const logger = Logger.create('MainScreen');
interface LayerModalState { interface LayerModalState {
visible: boolean; visible: boolean;
message: string; message: string;
@ -549,26 +545,8 @@ class MainScreenComponent extends React.Component<Props, State> {
bridge().restart(); bridge().restart();
}; };
const onInvitationRespond = async (shareUserId: string, accept: boolean) => { const onInvitationRespond = async (shareUserId: string, folderId: string, accept: boolean) => {
// The below functions can take a bit of time to complete so in the await invitationRespond(shareUserId, folderId, accept);
// meantime we hide the notification so that the user doesn't click
// multiple times on the Accept link.
ShareService.instance().setProcessingShareInvitationResponse(true);
try {
await ShareService.instance().respondInvitation(shareUserId, accept);
} catch (error) {
logger.error(error);
alert(_('Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: "%s"', error.message));
}
try {
await ShareService.instance().refreshShareInvitations();
} finally {
ShareService.instance().setProcessingShareInvitationResponse(false);
}
void reg.scheduleSync(1000);
}; };
let msg = null; let msg = null;
@ -613,9 +591,9 @@ class MainScreenComponent extends React.Component<Props, State> {
msg = this.renderNotificationMessage( msg = this.renderNotificationMessage(
_('%s (%s) would like to share a notebook with you.', sharer.full_name, sharer.email), _('%s (%s) would like to share a notebook with you.', sharer.full_name, sharer.email),
_('Accept'), _('Accept'),
() => onInvitationRespond(invitation.id, true), () => onInvitationRespond(invitation.id, invitation.share.folder_id, true),
_('Reject'), _('Reject'),
() => onInvitationRespond(invitation.id, false) () => onInvitationRespond(invitation.id, invitation.share.folder_id, false)
); );
} else if (this.props.hasDisabledSyncItems) { } else if (this.props.hasDisabledSyncItems) {
msg = this.renderNotificationMessage( msg = this.renderNotificationMessage(

View File

@ -1,6 +1,6 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale'; import { _ } from '@joplin/lib/locale';
import Folder from '@joplin/lib/models/Folder'; import ShareService from '@joplin/lib/services/share/ShareService';
export const declaration: CommandDeclaration = { export const declaration: CommandDeclaration = {
name: 'leaveSharedFolder', name: 'leaveSharedFolder',
@ -12,16 +12,7 @@ export const runtime = (): CommandRuntime => {
execute: async (_context: CommandContext, folderId: string = null) => { execute: async (_context: CommandContext, folderId: string = null) => {
const answer = confirm(_('This will remove the notebook from your collection and you will no longer have access to its content. Do you wish to continue?')); const answer = confirm(_('This will remove the notebook from your collection and you will no longer have access to its content. Do you wish to continue?'));
if (!answer) return; if (!answer) return;
await ShareService.instance().leaveSharedFolder(folderId);
// In that case, we should only delete the folder but none of its
// children. Deleting the folder tells the server that we want to
// leave the share. The server will then proceed to delete all
// associated user_items. So eventually all the notebook content
// will also be deleted for the current user.
//
// We don't delete the children here because that would delete them
// for the other share participants too.
await Folder.delete(folderId, { deleteChildren: false });
}, },
enabledCondition: 'joplinServerConnected && folderIsShareRootAndNotOwnedByUser', enabledCondition: 'joplinServerConnected && folderIsShareRootAndNotOwnedByUser',
}; };

View File

@ -0,0 +1,60 @@
import ShareService from '@joplin/lib/services/share/ShareService';
import Logger from '@joplin/lib/Logger';
import Folder from '@joplin/lib/models/Folder';
import { reg } from '@joplin/lib/registry';
import { _ } from '@joplin/lib/locale';
const logger = Logger.create('invitationRespond');
export default async function(shareUserId: string, folderId: string, accept: boolean) {
// The below functions can take a bit of time to complete so in the
// meantime we hide the notification so that the user doesn't click
// multiple times on the Accept link.
ShareService.instance().setProcessingShareInvitationResponse(true);
try {
await ShareService.instance().respondInvitation(shareUserId, accept);
} catch (error) {
logger.error(error);
alert(_('Could not respond to the invitation. Please try again, or check with the notebook owner if they are still sharing it.\n\nThe error was: "%s"', error.message));
}
// This is to handle an edge case that can happen if:
//
// - The user is a recipient of a share.
// - The sender removes the recipient from the share, then add him again.
// - The recipient gets the invitation, but reply "Reject" to it.
//
// If we don't handle this case, it would kind of work but would create
// conflicts because the shared notes would be converted to local ones, then
// during sync the synchronizer would try to delete them. Since they've been
// changed, they'll all be marked as conflicts.
//
// So the simplest thing to do is to leave the folder, which is most likely
// what the user wants. And if not, it's always possible to ask the sender
// to share again.
//
// NOTE: DOESN'T WORK. Because Folder.updateAllShareIds() would still run
// and change the notes share_id property, thus creating conflicts again.
// Leaving it as it is for now, as it's an unlikely scenario and it won't
// cause any data loss.
console.info('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', shareUserId, folderId);
console.info('ALL', await Folder.all());
if (!accept) {
const existingFolder = await Folder.load(folderId);
if (existingFolder) {
logger.warn('Rejected an invitation, but the folder was already there. Conflicts are likely to happen. ShareUserId:', shareUserId, 'Folder ID:', folderId);
// await ShareService.instance().leaveSharedFolder(folderId);
}
}
try {
await ShareService.instance().refreshShareInvitations();
} finally {
ShareService.instance().setProcessingShareInvitationResponse(false);
}
void reg.scheduleSync(1000);
}

View File

@ -117,6 +117,20 @@ export default class ShareService {
await Folder.updateAllShareIds(); await Folder.updateAllShareIds();
} }
// This is when a share recipient decides to leave the shared folder.
//
// In that case, we should only delete the folder but none of its children.
// Deleting the folder tells the server that we want to leave the share. The
// server will then proceed to delete all associated user_items. So
// eventually all the notebook content will also be deleted for the current
// user.
//
// We don't delete the children here because that would delete them for the
// other share participants too.
public async leaveSharedFolder(folderId: string): Promise<void> {
await Folder.delete(folderId, { deleteChildren: false });
}
public async shareNote(noteId: string): Promise<StateShare> { public async shareNote(noteId: string): Promise<StateShare> {
const note = await Note.load(noteId); const note = await Note.load(noteId);
if (!note) throw new Error(`No such note: ${noteId}`); if (!note) throw new Error(`No such note: ${noteId}`);