Skip to content

Commit 65fab90

Browse files
committed
fix(dialog): guard missing recipient permissions and refine the recipient row
- Guard recipient.permissions (the backend does not yet return per-recipient permissions), which previously threw on an undefined .map(). - Recipient row: drop the inline preset select; show a static preset label ("Can view" / "Custom permissions"), and move preset shortcuts + custom permissions (opens the modal) + remove into the three-dot menu. - Hide the Invited/Anyone tab bar once a recipient (or the link) exists. - Constrain the "Add people" field width. Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
1 parent 26a62cb commit 65fab90

3 files changed

Lines changed: 43 additions & 23 deletions

File tree

lib/dialog/components/RecipientRow.vue

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,27 @@
1515

1616
<div class="recipient-row__desc">
1717
<span class="recipient-row__name">{{ recipient.display_name }}</span>
18-
<NcSelect
19-
class="recipient-row__preset"
20-
:modelValue="selectedPreset"
21-
:clearable="false"
22-
:searchable="false"
23-
:inputLabel="t('Permissions')"
24-
:hideLabel="true"
25-
:options="presetOptions"
26-
:placeholder="t('Can…')"
27-
@update:modelValue="onPresetChange" />
18+
<span class="recipient-row__subtitle">{{ currentPresetLabel }}</span>
2819
</div>
2920

30-
<NcActions class="recipient-row__actions" :aria-label="t('Recipient actions')">
21+
<NcActions class="recipient-row__actions" :aria-label="t('Recipient actions')" :forceMenu="true">
22+
<NcActionCaption :name="t('Permissions')" />
23+
<NcActionButton
24+
v-for="preset in presets"
25+
:key="preset.value"
26+
@click="onPresetChange(preset)">
27+
<template #icon>
28+
<NcIconSvgWrapper v-if="preset.value === currentPresetValue" :svg="IconCheck" :size="20" />
29+
</template>
30+
{{ preset.label }}
31+
</NcActionButton>
3132
<NcActionButton @click="modalOpen = true">
3233
<template #icon>
33-
<NcIconSvgWrapper :svg="IconTune" :size="20" />
34+
<NcIconSvgWrapper :svg="isCustom ? IconCheck : IconTune" :size="20" />
3435
</template>
3536
{{ t('Custom permissions') }}
3637
</NcActionButton>
38+
<NcActionSeparator />
3739
<NcActionButton @click="remove">
3840
<template #icon>
3941
<NcIconSvgWrapper :svg="IconDelete" :size="20" />
@@ -65,19 +67,22 @@
6567

6668
<script setup lang="ts">
6769
import type { Share } from '../api/share.ts'
70+
import type { PresetOption } from '../composables/useRecipientPermissions.ts'
6871
import type { SharingRecipient } from '../types/api.ts'
6972
73+
import IconCheck from '@mdi/svg/svg/check.svg?raw'
7074
import IconDelete from '@mdi/svg/svg/delete.svg?raw'
7175
import IconTune from '@mdi/svg/svg/tune-variant.svg?raw'
7276
import { computed, ref } from 'vue'
7377
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
78+
import NcActionCaption from '@nextcloud/vue/components/NcActionCaption'
7479
import NcActions from '@nextcloud/vue/components/NcActions'
80+
import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
7581
import NcAvatar from '@nextcloud/vue/components/NcAvatar'
7682
import NcDialog from '@nextcloud/vue/components/NcDialog'
7783
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
78-
import NcSelect from '@nextcloud/vue/components/NcSelect'
7984
import PermissionEditor from './PermissionEditor.vue'
80-
import { useRecipientPermissions } from '../composables/useRecipientPermissions.ts'
85+
import { CUSTOM_VALUE, useRecipientPermissions } from '../composables/useRecipientPermissions.ts'
8186
import { RECIPIENT_TYPE_USER } from '../constants.ts'
8287
import { t } from '../utils/l10n.ts'
8388
import { logger } from '../utils/logger.ts'
@@ -105,6 +110,13 @@ const {
105110
onPermissionToggle,
106111
} = useRecipientPermissions(props.share, () => props.recipient)
107112
113+
const currentPresetValue = computed(() => selectedPreset.value.value)
114+
const isCustom = computed(() => currentPresetValue.value === CUSTOM_VALUE)
115+
// Preset shortcuts in the menu, excluding the "custom" sentinel.
116+
const presets = computed<PresetOption[]>(() => presetOptions.value.filter((option) => option.value !== CUSTOM_VALUE))
117+
// Static label shown under the recipient name.
118+
const currentPresetLabel = computed(() => isCustom.value ? t('Custom permissions') : selectedPreset.value.label)
119+
108120
/**
109121
* Remove this recipient from the share.
110122
*/
@@ -129,21 +141,18 @@ async function remove() {
129141
flex-direction: column;
130142
flex: 1 1 auto;
131143
min-width: 0;
144+
line-height: 1.2em;
132145
}
133146
134-
&__name {
147+
&__name,
148+
&__subtitle {
135149
overflow: hidden;
136150
text-overflow: ellipsis;
137151
white-space: nowrap;
138152
}
139153
140-
// Compact, borderless preset dropdown sitting under the name.
141-
&__preset {
142-
min-width: 0;
143-
:deep(.vs__dropdown-toggle) {
144-
border: none;
145-
color: var(--color-text-maxcontrast);
146-
}
154+
&__subtitle {
155+
color: var(--color-text-maxcontrast);
147156
}
148157
149158
&__actions {

lib/dialog/components/SharePanel.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
<form class="share-panel" @submit.prevent>
77
<!-- First page view -->
88
<template v-if="!inSettings">
9+
<!-- Share type is committed once a recipient (or the link) exists -->
910
<NcRadioGroup
11+
v-if="!hasRecipients"
1012
class="share-panel__tab-bar"
1113
:modelValue="shareDialogTab"
1214
:label="t('Share type')"
@@ -216,6 +218,9 @@ const isLinkShare = computed(() => shareDialogTab.value === ShareDialogTab.Anyon
216218
// A share cannot be submitted without at least one recipient.
217219
const canSubmit = computed(() => props.share.recipients.length > 0)
218220
221+
// Once a recipient (or the link) exists, the share type is committed.
222+
const hasRecipients = computed(() => props.share.recipients.length > 0)
223+
219224
// Editable properties, permissions/presets, recipient search and link handling
220225
// live in dedicated composables; this component wires them to the template.
221226
const {
@@ -322,9 +327,14 @@ form.share-panel {
322327
// letting flex shrink (compress) them to fit the max-height.
323328
> * {
324329
flex-shrink: 0;
330+
min-width: 0;
325331
}
326332
}
327333
334+
.share-panel__recipient-search {
335+
width: 100%;
336+
}
337+
328338
.share-panel__link-actions {
329339
display: flex;
330340
gap: calc(var(--default-grid-baseline) * 3);

lib/dialog/composables/useRecipientPermissions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ export function useRecipientPermissions(share: Share, getRecipient: () => Sharin
6767

6868
const permissions = computed<RecipientPermission[]>(() => {
6969
const max = shareMax.value
70-
return recipient.value.permissions.map((permission) => ({
70+
// The backend may not (yet) return per-recipient permissions.
71+
return (recipient.value.permissions ?? []).map((permission) => ({
7172
...permission,
7273
available: max.has(permission.class),
7374
}))

0 commit comments

Comments
 (0)