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:
parent
a5f2fd8982
commit
8a7fa78c54
@ -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
3
.gitignore
vendored
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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',
|
||||||
};
|
};
|
||||||
|
60
packages/app-desktop/services/share/invitationRespond.ts
Normal file
60
packages/app-desktop/services/share/invitationRespond.ts
Normal 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);
|
||||||
|
}
|
@ -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}`);
|
||||||
|
Loading…
Reference in New Issue
Block a user