Initial release: DictIA v0.8.14-alpha (fork de Speakr, AGPL-3.0)
This commit is contained in:
659
static/js/modules/composables/sharing.js
Normal file
659
static/js/modules/composables/sharing.js
Normal file
@@ -0,0 +1,659 @@
|
||||
/**
|
||||
* Sharing composable
|
||||
* Handles public and internal sharing of recordings
|
||||
*/
|
||||
|
||||
export function useSharing(state, utils) {
|
||||
const {
|
||||
showShareModal, showSharesListModal, showShareDeleteModal,
|
||||
showUnifiedShareModal, recordingToShare, shareOptions,
|
||||
generatedShareLink, existingShareDetected, recordingPublicShares, isLoadingPublicShares,
|
||||
userShares, isLoadingShares, copiedShareId, shareToDelete, selectedRecording, recordings,
|
||||
internalShareUserSearch, internalShareSearchResults,
|
||||
internalShareRecording, internalSharePermissions, internalShareMaxPermissions,
|
||||
recordingInternalShares, isLoadingInternalShares,
|
||||
isSearchingUsers, allUsers, isLoadingAllUsers,
|
||||
enableInternalSharing, showUsernamesInUI
|
||||
} = state;
|
||||
|
||||
const { showToast, setGlobalError } = utils;
|
||||
|
||||
let userSearchTimeout = null;
|
||||
|
||||
// Helper function to format share dates
|
||||
const formatShareDate = (dateString) => {
|
||||
if (!dateString) return 'Unknown date';
|
||||
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffMs = now - date;
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||
|
||||
// If today
|
||||
if (diffDays === 0) {
|
||||
return 'Today at ' + date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
|
||||
}
|
||||
// If yesterday
|
||||
else if (diffDays === 1) {
|
||||
return 'Yesterday at ' + date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
|
||||
}
|
||||
// If within last week
|
||||
else if (diffDays < 7) {
|
||||
return date.toLocaleDateString('en-US', { weekday: 'long', hour: 'numeric', minute: '2-digit', hour12: true });
|
||||
}
|
||||
// Otherwise show full date
|
||||
else {
|
||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error formatting date:', e);
|
||||
return dateString;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get color class for username (like speaker colors)
|
||||
const getUserColorClass = (username) => {
|
||||
if (!username) return 'speaker-color-1';
|
||||
|
||||
// Simple hash function to generate consistent color from username
|
||||
let hash = 0;
|
||||
for (let i = 0; i < username.length; i++) {
|
||||
hash = ((hash << 5) - hash) + username.charCodeAt(i);
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
|
||||
// Map to color classes 1-16
|
||||
const colorNum = (Math.abs(hash) % 16) + 1;
|
||||
return `speaker-color-${colorNum}`;
|
||||
};
|
||||
|
||||
// =========================================
|
||||
// Public Sharing
|
||||
// =========================================
|
||||
|
||||
const openShareModal = async (recording) => {
|
||||
recordingToShare.value = recording;
|
||||
shareOptions.share_summary = true;
|
||||
shareOptions.share_notes = true;
|
||||
generatedShareLink.value = '';
|
||||
existingShareDetected.value = false;
|
||||
recordingPublicShares.value = [];
|
||||
showShareModal.value = true;
|
||||
|
||||
// Load all public shares for this recording
|
||||
isLoadingPublicShares.value = true;
|
||||
try {
|
||||
const response = await fetch(`/api/shares`);
|
||||
if (response.ok) {
|
||||
const allShares = await response.json();
|
||||
// Filter to only shares for this recording and add share_url
|
||||
recordingPublicShares.value = allShares
|
||||
.filter(share => share.recording_id === recording.id)
|
||||
.map(share => ({
|
||||
...share,
|
||||
share_url: `${window.location.origin}/share/${share.public_id}`
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading public shares:', error);
|
||||
recordingPublicShares.value = [];
|
||||
} finally {
|
||||
isLoadingPublicShares.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const closeShareModal = () => {
|
||||
showShareModal.value = false;
|
||||
recordingToShare.value = null;
|
||||
existingShareDetected.value = false;
|
||||
recordingPublicShares.value = [];
|
||||
};
|
||||
|
||||
const createShare = async (forceNew = false) => {
|
||||
const recording = recordingToShare.value || internalShareRecording.value;
|
||||
if (!recording) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/recording/${recording.id}/share`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
...shareOptions,
|
||||
force_new: forceNew
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw new Error(data.error || 'Failed to create share link');
|
||||
|
||||
generatedShareLink.value = data.share_url;
|
||||
existingShareDetected.value = data.existing && !forceNew;
|
||||
|
||||
// Add to the shares list (works for both share modal and unified modal)
|
||||
if (!data.existing) {
|
||||
recordingPublicShares.value.push({
|
||||
...data.share,
|
||||
share_url: `${window.location.origin}/share/${data.share.public_id}`
|
||||
});
|
||||
// Update the recording's share count in the UI
|
||||
await refreshRecordingShareCounts();
|
||||
} else if (data.existing && !recordingPublicShares.value.find(s => s.id === data.share.id)) {
|
||||
// If existing but not in list, add it
|
||||
recordingPublicShares.value.push({
|
||||
...data.share,
|
||||
share_url: `${window.location.origin}/share/${data.share.public_id}`
|
||||
});
|
||||
}
|
||||
|
||||
if (data.existing && !forceNew) {
|
||||
showToast('Using existing share link', 'fa-link');
|
||||
} else {
|
||||
showToast('Share link created successfully!', 'fa-check-circle');
|
||||
}
|
||||
} catch (error) {
|
||||
setGlobalError(`Failed to create share link: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmDeletePublicShare = (share) => {
|
||||
shareToDelete.value = share;
|
||||
showShareDeleteModal.value = true;
|
||||
};
|
||||
|
||||
const deletePublicShare = async () => {
|
||||
if (!shareToDelete.value) return;
|
||||
const shareId = shareToDelete.value.id;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/share/${shareId}`, { method: 'DELETE' });
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw new Error(data.error || 'Failed to delete share');
|
||||
|
||||
// Remove from the shares list (both modals use different arrays)
|
||||
recordingPublicShares.value = recordingPublicShares.value.filter(s => s.id !== shareId);
|
||||
userShares.value = userShares.value.filter(s => s.id !== shareId);
|
||||
|
||||
// Update the recording's share count in the UI
|
||||
await refreshRecordingShareCounts();
|
||||
|
||||
showToast('Share link deleted successfully.', 'fa-check-circle');
|
||||
showShareDeleteModal.value = false;
|
||||
shareToDelete.value = null;
|
||||
} catch (error) {
|
||||
setGlobalError(`Failed to delete share: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const copyPublicShareLink = (shareUrl) => {
|
||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||
showToast('Share link copied to clipboard!', 'fa-check-circle');
|
||||
}).catch(() => {
|
||||
setGlobalError('Failed to copy link to clipboard');
|
||||
});
|
||||
};
|
||||
|
||||
const copyPublicShareLinkWithFeedback = (shareUrl, shareId) => {
|
||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||
copiedShareId.value = shareId;
|
||||
showToast('Share link copied to clipboard!', 'fa-check-circle');
|
||||
|
||||
// Reset after delay
|
||||
setTimeout(() => {
|
||||
copiedShareId.value = null;
|
||||
}, 1500);
|
||||
}).catch(() => {
|
||||
setGlobalError('Failed to copy link to clipboard');
|
||||
});
|
||||
};
|
||||
|
||||
const refreshRecordingShareCounts = async () => {
|
||||
// Refresh the current recording if one is selected
|
||||
const recording = recordingToShare.value || internalShareRecording.value || selectedRecording.value;
|
||||
if (!recording) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/recordings/${recording.id}`);
|
||||
if (response.ok) {
|
||||
const updatedRecording = await response.json();
|
||||
|
||||
// Update in recordings list
|
||||
const index = recordings.value.findIndex(r => r.id === recording.id);
|
||||
if (index !== -1) {
|
||||
// Preserve reactivity by updating specific fields
|
||||
recordings.value[index].public_share_count = updatedRecording.public_share_count || 0;
|
||||
recordings.value[index].shared_with_count = updatedRecording.shared_with_count || 0;
|
||||
}
|
||||
|
||||
// Update selected recording if it's the same one
|
||||
if (selectedRecording.value && selectedRecording.value.id === recording.id) {
|
||||
selectedRecording.value.public_share_count = updatedRecording.public_share_count || 0;
|
||||
selectedRecording.value.shared_with_count = updatedRecording.shared_with_count || 0;
|
||||
}
|
||||
|
||||
// Update internal share recording if it's the same one
|
||||
if (internalShareRecording.value && internalShareRecording.value.id === recording.id) {
|
||||
internalShareRecording.value.public_share_count = updatedRecording.public_share_count || 0;
|
||||
internalShareRecording.value.shared_with_count = updatedRecording.shared_with_count || 0;
|
||||
}
|
||||
|
||||
// Update recording to share if it's the same one
|
||||
if (recordingToShare.value && recordingToShare.value.id === recording.id) {
|
||||
recordingToShare.value.public_share_count = updatedRecording.public_share_count || 0;
|
||||
recordingToShare.value.shared_with_count = updatedRecording.shared_with_count || 0;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh recording share counts:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const copyShareLink = () => {
|
||||
if (!generatedShareLink.value) return;
|
||||
navigator.clipboard.writeText(generatedShareLink.value).then(() => {
|
||||
showToast('Share link copied to clipboard!');
|
||||
});
|
||||
};
|
||||
|
||||
const copyIndividualShareLink = (shareId) => {
|
||||
const input = document.getElementById(`share-link-${shareId}`);
|
||||
if (!input) return;
|
||||
|
||||
const button = input.nextElementSibling;
|
||||
if (!button) return;
|
||||
|
||||
navigator.clipboard.writeText(input.value).then(() => {
|
||||
copiedShareId.value = shareId;
|
||||
showToast('Share link copied to clipboard!', 'fa-check');
|
||||
|
||||
// Apply success state
|
||||
button.style.transition = 'background-color 0.2s ease';
|
||||
button.style.backgroundColor = 'var(--bg-success, #10b981)';
|
||||
|
||||
// Revert after delay
|
||||
setTimeout(() => {
|
||||
button.style.backgroundColor = '';
|
||||
copiedShareId.value = null;
|
||||
setTimeout(() => {
|
||||
button.style.transition = '';
|
||||
}, 200);
|
||||
}, 1500);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy share link:', err);
|
||||
});
|
||||
};
|
||||
|
||||
// =========================================
|
||||
// Shares List
|
||||
// =========================================
|
||||
|
||||
const openSharesList = async () => {
|
||||
isLoadingShares.value = true;
|
||||
showSharesListModal.value = true;
|
||||
try {
|
||||
const response = await fetch('/api/shares');
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw new Error(data.error || 'Failed to load shared items');
|
||||
userShares.value = data;
|
||||
} catch (error) {
|
||||
setGlobalError(`Failed to load shared items: ${error.message}`);
|
||||
} finally {
|
||||
isLoadingShares.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const closeSharesList = () => {
|
||||
showSharesListModal.value = false;
|
||||
userShares.value = [];
|
||||
};
|
||||
|
||||
const updateShare = async (share) => {
|
||||
try {
|
||||
const response = await fetch(`/api/share/${share.id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
share_summary: share.share_summary,
|
||||
share_notes: share.share_notes
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw new Error(data.error || 'Failed to update share');
|
||||
showToast('Share permissions updated.', 'fa-check-circle');
|
||||
} catch (error) {
|
||||
setGlobalError(`Failed to update share: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmDeleteShare = (share) => {
|
||||
shareToDelete.value = share;
|
||||
showShareDeleteModal.value = true;
|
||||
};
|
||||
|
||||
const cancelDeleteShare = () => {
|
||||
shareToDelete.value = null;
|
||||
showShareDeleteModal.value = false;
|
||||
};
|
||||
|
||||
// =========================================
|
||||
// Internal Sharing
|
||||
// =========================================
|
||||
|
||||
const loadAllUsers = async () => {
|
||||
if (!showUsernamesInUI.value) return;
|
||||
|
||||
isLoadingAllUsers.value = true;
|
||||
try {
|
||||
const response = await fetch('/api/users/search?q=');
|
||||
if (!response.ok) {
|
||||
if (response.status === 403) {
|
||||
throw new Error('Internal sharing is not enabled');
|
||||
}
|
||||
throw new Error('Failed to load users');
|
||||
}
|
||||
const data = await response.json();
|
||||
allUsers.value = data;
|
||||
} catch (error) {
|
||||
setGlobalError(`Failed to load users: ${error.message}`);
|
||||
allUsers.value = [];
|
||||
} finally {
|
||||
isLoadingAllUsers.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const searchInternalShareUsers = async () => {
|
||||
const query = internalShareUserSearch.value.trim();
|
||||
|
||||
// If SHOW_USERNAMES_IN_UI is enabled, filter allUsers locally
|
||||
if (showUsernamesInUI.value) {
|
||||
// Get list of user IDs that already have access
|
||||
const sharedUserIds = new Set(recordingInternalShares.value.map(share => share.user_id));
|
||||
|
||||
// Filter out already-shared users
|
||||
const availableUsers = allUsers.value.filter(user => !sharedUserIds.has(user.id));
|
||||
|
||||
if (query.length === 0) {
|
||||
internalShareSearchResults.value = availableUsers;
|
||||
} else {
|
||||
internalShareSearchResults.value = availableUsers.filter(user =>
|
||||
user.username.toLowerCase().includes(query.toLowerCase()) ||
|
||||
(user.email && user.email.toLowerCase().includes(query.toLowerCase()))
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, use server-side search
|
||||
if (query.length < 2) {
|
||||
internalShareSearchResults.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(userSearchTimeout);
|
||||
userSearchTimeout = setTimeout(async () => {
|
||||
isSearchingUsers.value = true;
|
||||
try {
|
||||
const response = await fetch(`/api/users/search?q=${encodeURIComponent(query)}`);
|
||||
if (!response.ok) {
|
||||
if (response.status === 403) {
|
||||
throw new Error('Internal sharing is not enabled');
|
||||
}
|
||||
throw new Error('Failed to search users');
|
||||
}
|
||||
const data = await response.json();
|
||||
internalShareSearchResults.value = data;
|
||||
} catch (error) {
|
||||
setGlobalError(`Failed to search users: ${error.message}`);
|
||||
internalShareSearchResults.value = [];
|
||||
} finally {
|
||||
isSearchingUsers.value = false;
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const openUnifiedShareModal = async (recording) => {
|
||||
internalShareRecording.value = recording;
|
||||
internalShareUserSearch.value = '';
|
||||
internalShareSearchResults.value = [];
|
||||
internalSharePermissions.value = { can_edit: false, can_reshare: false };
|
||||
recordingPublicShares.value = [];
|
||||
shareOptions.share_summary = true;
|
||||
shareOptions.share_notes = true;
|
||||
|
||||
// PERMISSION CEILING: Calculate maximum permissions current user can grant
|
||||
// If viewing a shared recording (not owner), constrain to their permissions
|
||||
if (recording.is_shared && recording.share_info) {
|
||||
internalShareMaxPermissions.value = {
|
||||
can_edit: recording.share_info.can_edit || false,
|
||||
can_reshare: recording.share_info.can_reshare || false
|
||||
};
|
||||
} else {
|
||||
// Owner has unlimited permissions
|
||||
internalShareMaxPermissions.value = {
|
||||
can_edit: true,
|
||||
can_reshare: true
|
||||
};
|
||||
}
|
||||
|
||||
showUnifiedShareModal.value = true;
|
||||
|
||||
// Load all public shares for this recording
|
||||
isLoadingPublicShares.value = true;
|
||||
try {
|
||||
const response = await fetch(`/api/shares`);
|
||||
if (response.ok) {
|
||||
const allShares = await response.json();
|
||||
// Filter to only shares for this recording and add share_url
|
||||
recordingPublicShares.value = allShares
|
||||
.filter(share => share.recording_id === recording.id)
|
||||
.map(share => ({
|
||||
...share,
|
||||
share_url: `${window.location.origin}/share/${share.public_id}`
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading public shares:', error);
|
||||
recordingPublicShares.value = [];
|
||||
} finally {
|
||||
isLoadingPublicShares.value = false;
|
||||
}
|
||||
|
||||
// Load existing internal shares
|
||||
isLoadingInternalShares.value = true;
|
||||
try {
|
||||
const response = await fetch(`/api/recordings/${recording.id}/shares-internal`);
|
||||
if (!response.ok) {
|
||||
if (response.status === 403) {
|
||||
throw new Error('Internal sharing is not enabled');
|
||||
}
|
||||
throw new Error('Failed to load shares');
|
||||
}
|
||||
const data = await response.json();
|
||||
recordingInternalShares.value = data.shares || [];
|
||||
} catch (error) {
|
||||
setGlobalError(`Failed to load shares: ${error.message}`);
|
||||
recordingInternalShares.value = [];
|
||||
} finally {
|
||||
isLoadingInternalShares.value = false;
|
||||
}
|
||||
|
||||
// If SHOW_USERNAMES_IN_UI is enabled, load all users
|
||||
if (showUsernamesInUI.value) {
|
||||
await loadAllUsers();
|
||||
internalShareSearchResults.value = allUsers.value;
|
||||
}
|
||||
};
|
||||
|
||||
const closeUnifiedShareModal = () => {
|
||||
showUnifiedShareModal.value = false;
|
||||
internalShareRecording.value = null;
|
||||
internalShareUserSearch.value = '';
|
||||
internalShareSearchResults.value = [];
|
||||
recordingInternalShares.value = [];
|
||||
recordingPublicShares.value = [];
|
||||
allUsers.value = [];
|
||||
};
|
||||
|
||||
// Legacy function names for backward compatibility
|
||||
const openInternalShareModal = openUnifiedShareModal;
|
||||
const openManageInternalSharesModal = openUnifiedShareModal;
|
||||
const closeInternalShareModal = closeUnifiedShareModal;
|
||||
const closeManageInternalSharesModal = closeUnifiedShareModal;
|
||||
|
||||
const reloadInternalShares = async () => {
|
||||
if (!internalShareRecording.value) return;
|
||||
|
||||
isLoadingInternalShares.value = true;
|
||||
try {
|
||||
const response = await fetch(`/api/recordings/${internalShareRecording.value.id}/shares-internal`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load shares');
|
||||
}
|
||||
const data = await response.json();
|
||||
recordingInternalShares.value = data.shares || [];
|
||||
} catch (error) {
|
||||
setGlobalError(`Failed to reload shares: ${error.message}`);
|
||||
} finally {
|
||||
isLoadingInternalShares.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const shareWithUsername = async () => {
|
||||
if (!internalShareRecording.value) return;
|
||||
|
||||
const username = internalShareUserSearch.value.trim();
|
||||
if (!username) {
|
||||
setGlobalError('Please enter a username');
|
||||
return;
|
||||
}
|
||||
|
||||
isSearchingUsers.value = true;
|
||||
try {
|
||||
// Search for the exact username
|
||||
const searchResponse = await fetch(`/api/users/search?q=${encodeURIComponent(username)}`);
|
||||
if (!searchResponse.ok) {
|
||||
if (searchResponse.status === 403) {
|
||||
throw new Error('Internal sharing is not enabled');
|
||||
}
|
||||
throw new Error('Failed to find user');
|
||||
}
|
||||
|
||||
const users = await searchResponse.json();
|
||||
|
||||
if (users.length === 0) {
|
||||
setGlobalError(`User "${username}" not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the first matching user (should be exact match from backend)
|
||||
const user = users[0];
|
||||
await createInternalShare(user.id, user.username);
|
||||
|
||||
// Clear input on success
|
||||
internalShareUserSearch.value = '';
|
||||
} catch (error) {
|
||||
setGlobalError(error.message || 'Failed to share with user');
|
||||
} finally {
|
||||
isSearchingUsers.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const createInternalShare = async (userId, username) => {
|
||||
if (!internalShareRecording.value) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/recordings/${internalShareRecording.value.id}/share-internal`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
user_id: userId,
|
||||
can_edit: internalSharePermissions.value.can_edit,
|
||||
can_reshare: internalSharePermissions.value.can_reshare
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to share recording');
|
||||
}
|
||||
|
||||
const displayName = showUsernamesInUI.value ? username : `User #${userId}`;
|
||||
showToast(`Recording shared with ${displayName}`, 'fa-share-alt');
|
||||
|
||||
// Reset permissions for next share
|
||||
internalSharePermissions.value = { can_edit: false, can_reshare: false };
|
||||
|
||||
// Reload shares to show the new share in the list
|
||||
await reloadInternalShares();
|
||||
|
||||
// Update the recording's share count in the UI
|
||||
await refreshRecordingShareCounts();
|
||||
} catch (error) {
|
||||
setGlobalError(`Failed to share recording: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const revokeInternalShare = async (shareId, username) => {
|
||||
if (!internalShareRecording.value) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/internal-shares/${shareId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
throw new Error(data.error || 'Failed to revoke share');
|
||||
}
|
||||
|
||||
recordingInternalShares.value = recordingInternalShares.value.filter(s => s.id !== shareId);
|
||||
const displayName = showUsernamesInUI.value ? username : 'User';
|
||||
showToast(`Access revoked for ${displayName}`, 'fa-user-times');
|
||||
|
||||
// Update the recording's share count in the UI
|
||||
await refreshRecordingShareCounts();
|
||||
} catch (error) {
|
||||
setGlobalError(`Failed to revoke share: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
// Utilities
|
||||
formatShareDate,
|
||||
getUserColorClass,
|
||||
|
||||
// Public sharing
|
||||
openShareModal,
|
||||
closeShareModal,
|
||||
createShare,
|
||||
copyShareLink,
|
||||
copyPublicShareLink,
|
||||
copyPublicShareLinkWithFeedback,
|
||||
copyIndividualShareLink,
|
||||
confirmDeletePublicShare,
|
||||
deletePublicShare,
|
||||
refreshRecordingShareCounts,
|
||||
|
||||
// Shares list
|
||||
openSharesList,
|
||||
closeSharesList,
|
||||
updateShare,
|
||||
confirmDeleteShare,
|
||||
cancelDeleteShare,
|
||||
deleteShare: deletePublicShare, // Alias for template compatibility
|
||||
copiedShareId,
|
||||
|
||||
// Internal sharing
|
||||
loadAllUsers,
|
||||
searchInternalShareUsers,
|
||||
openUnifiedShareModal,
|
||||
closeUnifiedShareModal,
|
||||
openInternalShareModal,
|
||||
closeInternalShareModal,
|
||||
openManageInternalSharesModal,
|
||||
closeManageInternalSharesModal,
|
||||
reloadInternalShares,
|
||||
shareWithUsername,
|
||||
createInternalShare,
|
||||
revokeInternalShare
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user