Skip to content

feat(dialog): experimental sharing dialog for the unified sharing API#239

Draft
skjnldsv wants to merge 21 commits into
mainfrom
feature/vite-lib-build
Draft

feat(dialog): experimental sharing dialog for the unified sharing API#239
skjnldsv wants to merge 21 commits into
mainfrom
feature/vite-lib-build

Conversation

@skjnldsv

@skjnldsv skjnldsv commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

skjnldsv added 2 commits July 17, 2026 16:12
Switch the build from plain tsc to @nextcloud/vite-config createLibConfig
so the package can ship Vue SFCs in upcoming subpath exports. The three
existing entry points (., ./public, ./ui) are preserved; only the dist
file names change (.js -> .mjs, ui/index.js -> ui.mjs). Declarations are
generated by vite-plugin-dts and rolled up in production mode.

Also fixes the stale lib/publicShare.ts reference in build:doc.

Assisted-by: ClaudeCode:claude-fable-5
… API

Add a new ./dialog entry point shipping the sharing dialog built for the
unified sharing API (Nextcloud >= 35):

- openSharingDialog(node) spawns the dialog, gated on the sharing
  capability being present
- SharingDialog component export for embedding
- typed API client (createShare, addShareRecipient,
  selectSharePermissionPreset, ...) and response types
- own gettext domain, translations bundled at build time

The dialog is marked experimental: the server API is new and the module
API may change in any minor release.

Assisted-by: ClaudeCode:claude-fable-5
@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.89286% with 27 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.42%. Comparing base (7162b64) to head (6a7e5c4).
⚠️ Report is 80 commits behind head on main.

Files with missing lines Patch % Lines
lib/dialog/utils/api.ts 19.04% 11 Missing and 6 partials ⚠️
lib/dialog/components/PropertyField.vue 87.30% 8 Missing ⚠️
lib/dialog/types/api.ts 0.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #239       +/-   ##
===========================================
- Coverage   95.65%   83.42%   -12.23%     
===========================================
  Files           4       11        +7     
  Lines          69      181      +112     
  Branches       25       68       +43     
===========================================
+ Hits           66      151       +85     
- Misses          3       24       +21     
- Partials        0        6        +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

skjnldsv added 15 commits July 17, 2026 17:13
Declaration rollup only ran in production mode, so dev/watch builds
emitted nested d.ts files that did not match the flat paths in the
exports map. Drop the rollup and point exports at the nested output,
identical in both modes.

Assisted-by: ClaudeCode:claude-fable-5
The unified sharing API replaced the view/edit preset enum with
registered preset classes carrying translated display names:

- capabilities now expose sharing.permission_presets with
  {class, display_name, hint}, so the hardcoded preset label map is
  gone and the dropdown is fully backend-driven
- permission.presets and permission_preset carry preset class strings
- the preset endpoint parameter was renamed to permissionPresetClass

Assisted-by: ClaudeCode:claude-fable-5
Render the date property with NcTextField (type=date) instead of
NcDateTimePickerNative so it shares the floating-label design of the
other fields (label, password). ISO values are reduced to their date
part for the native input and re-expanded to an ATOM datetime on save.

type=date needs the NcInputField date-type support (@nextcloud/vue
> 9.9.0); it already works at runtime on 9.9.0, so the type-check is
suppressed with a versioned @vue-expect-error until that lands.

Assisted-by: ClaudeCode:claude-fable-5
The floating-label design now lives in NcDateTimePickerNative itself, so
the dialog no longer needs the NcTextField workaround (and its
type-check suppression). Restore the Date-based picker usage.

Assisted-by: ClaudeCode:claude-fable-5
- word-break: break-all on the title so long file names wrap instead of
  overflowing
- scroll the share form instead of the whole dialog content, so the
  scrollbar no longer overlaps the fixed header and its close button

Assisted-by: ClaudeCode:claude-fable-5
Admin-enforced properties (required, no toggle) now show a small info
icon next to the field with a native tooltip carrying the property hint,
e.g. why an expiration date or password is mandatory. The wording is
provided by the backend property hint, so it stays accurate per share
type and translatable.

Assisted-by: ClaudeCode:claude-fable-5
The earlier form-scroll override fought the content-sized modal: it
collapsed the height (constricted layout) and showed a scrollbar even
when everything fit. Revert to the dialog's native content scrolling and
instead pad the form bottom to match the dialog's inline padding.

Assisted-by: ClaudeCode:claude-fable-5
Cap the form with a max-height and scroll it, instead of flexing it
against the content-sized modal. The header and its close button stay
fixed above the scroll region, while the dialog still sizes to its
content (no spurious scrollbar) when short.

Assisted-by: ClaudeCode:claude-fable-5
In a scrolling flex column the fields were shrinking (flex-shrink) to
fit the max-height, compressing the inputs. Pin the direct children to
their natural height so the form scrolls instead.

Assisted-by: ClaudeCode:claude-fable-5
- drop the form margin in favour of a dialog content gap
- form gap 4x -> 3x the grid baseline, unified with the content gap and
  bottom padding (all calc(var(--default-grid-baseline) * 3))
- express the header gap, settings-toggle offset and empty-content
  padding as grid-baseline multiples instead of raw pixels

Assisted-by: ClaudeCode:claude-fable-5
Floating input labels overshoot upward into the form gap, so the 3x
baseline gap looked cramped between selects. Bump the form gap to 4x the
baseline (the content/header gap stays at 3x, no floating label there).

Assisted-by: ClaudeCode:claude-fable-5
The recipient model (selectedRecipient) and its watcher are single-value
(a new pick replaces the previous), but NcSelectUsers defaulted to
multiple mode. Multiple mode has no height boost, so the picker rendered
short (~33px). Set multiple=false to match the model and get the proper
42px single-select height.

Also reverts the temporary form-gap bump; the spacing was a symptom of
the short picker, not the gap.

Assisted-by: ClaudeCode:claude-fable-5
The inactive-slot styles were lost in the port: the disabled input
swallowed the click so the field never enabled, and there was no pointer
cursor. Restore the inactive rule — pointer cursor + pointer-events:none
on the slot content so the click reaches the container's enable handler.

Assisted-by: ClaudeCode:claude-fable-5
Replace the hand-rolled OCS envelope shapes (unwrapOcs, the OCS error
type, and the test helper) with the shared OCSResponse<T> type from
@nextcloud/typings/ocs. Declare the (types-only) dev dependency.

Assisted-by: ClaudeCode:claude-fable-5
@skjnldsv skjnldsv self-assigned this Jul 22, 2026
@skjnldsv skjnldsv added the 2. developing Work in progress label Jul 22, 2026
Comment thread README.md
Comment thread lib/dialog/utils/logger.ts
Comment thread lib/dialog/index.ts Outdated
Comment thread lib/dialog/constants.ts
Comment thread lib/dialog/utils/api.ts Outdated
Comment thread lib/dialog/types/api.ts
skjnldsv added 4 commits July 23, 2026 10:09
Addresses PR review note.

Assisted-by: ClaudeCode:claude-opus-4-8
openSharingDialog no longer throws when the unified sharing API is
missing; it shows an error toast and resolves to undefined, so callers
can invoke it unconditionally. isSharingDialogAvailable stays exported
for gating the entry point.

Addresses PR review note.

Assisted-by: ClaudeCode:claude-opus-4-8
Generate recipient display variations on the frontend, mirroring the
legacy files_sharing SharingInput: email/group/team recipients get a
type-specific mdi icon, federated recipients show their instance on a
second line, and email recipients show the address. Users keep native
avatar loading; unknown types fall back to a backend-provided icon.

Adds recipient type constants and unit tests for the mapping.

Assisted-by: ClaudeCode:claude-opus-4-8
Outline mdi variants are the current design pattern. Switch the
recipient type icons (email/group/team) and the send action icon to
their -outline variants. content-copy and arrow-left have no outline
variant and are left unchanged.

Assisted-by: ClaudeCode:claude-opus-4-8
Comment on lines +38 to +49
const props = defineProps<{
/** Accessible label of the field group and its toggle */
label: string
/** Whether the field is enabled */
modelValue: boolean
/** Align the toggle to the first line of a multi-line field (e.g. textarea) */
longText?: boolean
}>()

const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const props = defineProps<{
/** Accessible label of the field group and its toggle */
label: string
/** Whether the field is enabled */
modelValue: boolean
/** Align the toggle to the first line of a multi-line field (e.g. textarea) */
longText?: boolean
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
/** Whether the field is enabled */
const modelValue = defineModel<boolean>()
const props = defineProps<{
/** Accessible label of the field group and its toggle */
label: string
/** Align the toggle to the first line of a multi-line field (e.g. textarea) */
longText?: boolean
}>()

consistent with other component

import { computed, nextTick, ref } from 'vue'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'

defineOptions({ name: 'InlineToggleField' })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should already be the case for Vue3? It should be inherited by the filename

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me it never works 😅😅
But with the two different Vue extensions, it might be the root cause tbh

:aria-label="t('Back to sharing options')"
@click="inSettings = false">
<template #icon>
<NcIconSvgWrapper :svg="IconArrowLeft" name="Back" :size="20" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<NcIconSvgWrapper :svg="IconArrowLeft" name="Back" :size="20" />
<NcIconSvgWrapper :svg="IconArrowLeft" directional />
  1. no name because button has already an accessible name
  2. make it directional as on RTL it should be mirrored
  3. 20px is default

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never knew about directional, very cool 😎
Thanks!!

:inSettings="inSettings"
:share="share"
:shareDialogTab="shareDialogTab"
@settingsWarning="(v) => (settingsHasWarning = v)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine but if you like you can shorten it

Suggested change
@settingsWarning="(v) => (settingsHasWarning = v)"
@settingsWarning="settingsHasWarning = $event"

variant="tertiary"
@click="inSettings = true">
<template #icon>
<NcIconSvgWrapper :svg="IconCogOutline" name="Settings" :size="20" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here no name if the button already has a label (and its not translated)

Suggested change
<NcIconSvgWrapper :svg="IconCogOutline" name="Settings" :size="20" />
<NcIconSvgWrapper :svg="IconCogOutline" />

Comment on lines +436 to +446
const textarea = document.createElement('textarea')
textarea.value = text
textarea.style.position = 'fixed'
textarea.style.opacity = '0'
document.body.appendChild(textarea)
textarea.select()
try {
document.execCommand('copy')
} finally {
document.body.removeChild(textarea)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In many browsers document.execCommand('copy') does not work anymore as deprecated.

Suggested change
const textarea = document.createElement('textarea')
textarea.value = text
textarea.style.position = 'fixed'
textarea.style.opacity = '0'
document.body.appendChild(textarea)
textarea.select()
try {
document.execCommand('copy')
} finally {
document.body.removeChild(textarea)
}
window.prompt(t('Please copy the share link manually'), text)

Comment thread lib/dialog/index.ts
*
* @param node The file or folder to share
*/
export async function openSharingDialog(node: INode): Promise<unknown> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export async function openSharingDialog(node: INode): Promise<unknown> {
export async function openSharingDialog(node: INode[]): Promise<unknown> {

Should use an array to later properly have support for multi-node shares without api break

Comment thread README.md
import { openSharingDialog, isSharingDialogAvailable } from '@nextcloud/sharing/dialog'

// Safe to call directly: shows an error toast if the API is unavailable
await openSharingDialog(node)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await openSharingDialog(node)
await openSharingDialog([node])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2. developing Work in progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants