From 25d21b93ffe06abccb20875f2c48e247371543cd Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Sun, 9 Aug 2026 13:17:40 +1000 Subject: [PATCH 1/3] Add delete functionality to 3 dots menu, distinguish container resource 3 dots Note: tests were ai generated based on specific prompts Co-authored-by: GPT-5.4 Mini --- package.json | 2 +- .../FileExplorerHeader.ts | 42 +++--- .../FileExplorerHeaderControls.ts | 38 +++++- .../FileExplorerHeaderSummary.ts | 11 +- .../FileExplorerProvider.ts | 38 +++++- src/components/file-explorer-header/helper.ts | 125 ++++-------------- src/components/file-explorer-header/types.ts | 10 ++ .../DeleteResourceDialog.styles.css | 12 ++ .../DeleteResourceDialog.ts | 41 ++++-- .../ResourceActionsMenu.ts | 101 +++++++------- src/outline/manager.js | 12 ++ .../components/delete-resource-dialog.test.ts | 15 +++ .../components/file-explorer-provider.test.ts | 76 +++++++++++ .../components/resource-actions-menu.test.ts | 42 ++++++ 14 files changed, 386 insertions(+), 179 deletions(-) create mode 100644 src/components/resource-actions-menu/DeleteResourceDialog.styles.css create mode 100644 test/unit/components/delete-resource-dialog.test.ts create mode 100644 test/unit/components/file-explorer-provider.test.ts create mode 100644 test/unit/components/resource-actions-menu.test.ts diff --git a/package.json b/package.json index 12f293f5..d11eadf6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-panes", - "version": "4.4.2-7", + "version": "4.4.2-8", "description": "Solid-compatible Panes: applets and views for the mashlib and databrowser", "main": "dist/index.cjs.js", "types": "dist/index.d.ts", diff --git a/src/components/file-explorer-header/FileExplorerHeader.ts b/src/components/file-explorer-header/FileExplorerHeader.ts index 136cdc3f..82110e8e 100644 --- a/src/components/file-explorer-header/FileExplorerHeader.ts +++ b/src/components/file-explorer-header/FileExplorerHeader.ts @@ -3,14 +3,14 @@ import { WebComponent, type FileExplorerContext, fileExplorerContext } from 'sol import { customElement, property, state } from 'lit/decorators.js' import { consume } from '@lit/context' import { html } from 'lit' +import { solidLogicSingleton } from 'solid-logic' import '~icons/lucide/share-2' import '~icons/lucide/pencil' import '~icons/lucide/ellipsis-vertical' import styles from './FileExplorerHeader.styles.css' import './FileExplorerHeaderSummary' import './FileExplorerHeaderControls' -import { PaneIcon } from './types' -import { fetchContentAndMetadata, type FileExplorerResourceMetadata } from './helper' +import { PaneIcon, type FileExplorerHeaderMetadata } from './types' @customElement('file-explorer-header') export default class FileExplorerHeader extends WebComponent { @@ -27,19 +27,28 @@ export default class FileExplorerHeader extends WebComponent { @property({ attribute: false }) accessor paneIcon: PaneIcon = undefined as unknown as PaneIcon + @property({ type: Boolean }) + accessor isContainerResource: boolean = false + @state() - accessor responseMetadata: Pick = { + accessor responseMetadata: FileExplorerHeaderMetadata = { modified: undefined, - isPublic: false, - canEdit: false, + access: { + isPublic: false, + canEdit: false, + canDelete: false + }, aclUri: undefined } - private getDefaultResponseMetadata (): Pick { + private getDefaultResponseMetadata (): FileExplorerHeaderMetadata { return { modified: undefined, - isPublic: false, - canEdit: false, + access: { + isPublic: false, + canEdit: false, + canDelete: false + }, aclUri: undefined } } @@ -54,17 +63,18 @@ export default class FileExplorerHeader extends WebComponent { private async loadResponseMetadata () { if (!this.fileExplorerContext?.store || !this.fileExplorerContext.subjectUri) return + const subjectUri = this.fileExplorerContext.subjectUri + const defaultMetadata = this.getDefaultResponseMetadata() + try { - const { metadata } = await fetchContentAndMetadata(this.fileExplorerContext.store, sym(this.fileExplorerContext.subjectUri)) + const metadata = await solidLogicSingleton.resource.fetchMetadataWithDelete(sym(subjectUri)) this.responseMetadata = { - modified: metadata.modified, - isPublic: metadata.isPublic, - canEdit: metadata.canEdit, - aclUri: metadata.aclUri + modified: metadata?.modified ?? defaultMetadata.modified, + access: metadata?.access ?? defaultMetadata.access, + aclUri: metadata?.aclUri ?? defaultMetadata.aclUri } } catch (error) { this.responseMetadata = this.getDefaultResponseMetadata() - console.warn('Failed to load response metadata', error) } } @@ -78,7 +88,9 @@ export default class FileExplorerHeader extends WebComponent { > ` diff --git a/src/components/file-explorer-header/FileExplorerHeaderControls.ts b/src/components/file-explorer-header/FileExplorerHeaderControls.ts index b6668ce4..36059187 100644 --- a/src/components/file-explorer-header/FileExplorerHeaderControls.ts +++ b/src/components/file-explorer-header/FileExplorerHeaderControls.ts @@ -22,6 +22,12 @@ export default class FileExplorerHeaderControls extends WebComponent { @property({ type: Boolean }) accessor canEdit: boolean = false + @property({ type: Boolean }) + accessor canDelete: boolean = false + + @property({ type: Boolean }) + accessor isContainerResource: boolean = false + // TODO: Add broken then use this function to set tooltip and disable edit button /* private setEditable() { const sourcePaneState = this.sourceContext?.sourcePaneState @@ -43,12 +49,31 @@ export default class FileExplorerHeaderControls extends WebComponent { } private renderDirtyIndicator () { - if (!this.fileExplorerContext.edit?.isDirty) return nothing + if (!this.fileExplorerContext.paneSupportsEditing || !this.fileExplorerContext.edit?.isDirty) return nothing return html`Unsaved` } - render () { + private renderContainerControl () { + if (!this.fileExplorerContext.subjectUri || !this.fileExplorerContext.store) return nothing + + return html` +
+ +
+ ` + } + + private renderResourceControls () { + if (!this.fileExplorerContext.subjectUri || !this.fileExplorerContext.store) return nothing + return html`
${this.renderDirtyIndicator()} @@ -66,9 +91,18 @@ export default class FileExplorerHeaderControls extends WebComponent {
` } + + render () { + return html` + ${this.isContainerResource ? this.renderContainerControl() : this.renderResourceControls()} + ` + } } diff --git a/src/components/file-explorer-header/FileExplorerHeaderSummary.ts b/src/components/file-explorer-header/FileExplorerHeaderSummary.ts index a02b6947..43a1c48a 100644 --- a/src/components/file-explorer-header/FileExplorerHeaderSummary.ts +++ b/src/components/file-explorer-header/FileExplorerHeaderSummary.ts @@ -9,7 +9,7 @@ import '~icons/lucide/globe' import '~icons/lucide/lock-keyhole' import '~icons/lucide/arrow-left' import styles from './FileExplorerHeaderSummary.styles.css' -import { type FileExplorerResourceMetadata } from './helper' +import { type FileExplorerHeaderMetadata } from './types' @customElement('file-explorer-header-summary') export default class FileExplorerHeaderSummary extends WebComponent { @@ -28,9 +28,12 @@ export default class FileExplorerHeaderSummary extends WebComponent { accessor onBackClick: (() => void) | undefined @property({ attribute: false }) - accessor responseMetadata: Pick = { + accessor responseMetadata: Pick = { modified: undefined, - isPublic: false + access: { + isPublic: false, + canEdit: false + } } @state() @@ -97,7 +100,7 @@ export default class FileExplorerHeaderSummary extends WebComponent { const subject = this.fileExplorerContext?.subjectUri ? sym(this.fileExplorerContext.subjectUri) : undefined const label = subject ? utils.label(subject) : '' const modified = this.formatModifiedDate(this.responseMetadata.modified) - const isPublic = this.responseMetadata.isPublic + const isPublic = this.responseMetadata.access.isPublic return html`
diff --git a/src/components/file-explorer-header/FileExplorerProvider.ts b/src/components/file-explorer-header/FileExplorerProvider.ts index 07ea4c24..40773b5a 100644 --- a/src/components/file-explorer-header/FileExplorerProvider.ts +++ b/src/components/file-explorer-header/FileExplorerProvider.ts @@ -9,6 +9,8 @@ import './FileExplorerHeader' import styles from './FileExplorerProvider.styles.css' import personIcon from '../../icons/person.svg' import friendsIcon from '../../icons/friends.svg' +import { solidLogicSingleton } from 'solid-logic' +import { deriveDeleteTargetUri } from './helper' const PERSON_ICON = personIcon const FRIENDS_ICON = friendsIcon @@ -19,6 +21,8 @@ function createFileExplorerContextValue (value: { pane?: PaneDefinition soloPane?: boolean onBack?: () => void + refresh?: () => void + deleteTargetUri?: string openPane?: (subject: NamedNode, paneName: string) => void handleSharingClick?: () => void paneSupportsEditing?: boolean @@ -34,6 +38,8 @@ function createFileExplorerContextValue (value: { pane: value.pane, soloPane: value.soloPane, onBack: value.onBack, + refresh: value.refresh, + deleteTargetUri: value.deleteTargetUri, openPane: value.openPane, handleSharingClick: value.handleSharingClick, paneSupportsEditing: value.paneSupportsEditing, @@ -53,6 +59,12 @@ export default class FileExplorerProvider extends WebComponent { @property({ attribute: false }) accessor onBack: (() => void) | undefined = undefined + @property({ attribute: false }) + accessor refresh: (() => void) | undefined = undefined + + @property({ attribute: false }) + accessor deleteTargetUri: string | undefined = undefined + @property({ attribute: false }) accessor relevantPanes: PaneDefinition[] = [] @@ -87,6 +99,9 @@ export default class FileExplorerProvider extends WebComponent { @state() accessor paneSupportsEditing: boolean = false + @state() + accessor isContainerResourceValue: boolean = false + // TODO: For now this works, but check if there is a better way. // because this means file explorer will know about the pane. // what if other panes want to use this. If so maybe we should @@ -124,6 +139,8 @@ export default class FileExplorerProvider extends WebComponent { pane: this.pane, soloPane: this.soloPane, onBack: this.onBack, + refresh: this.refresh, + deleteTargetUri: deriveDeleteTargetUri(this.context?.session.store as LiveStore, this.subjectUri, this.pane?.mintClass, this.deleteTargetUri), openPane: this.openPane, handleSharingClick: this.handleSharingClick, paneSupportsEditing: false, @@ -138,11 +155,12 @@ export default class FileExplorerProvider extends WebComponent { this.pane = pane } - private getPaneIcon (pane, subject, context) { + // The icon method may be a function that returns a promise, so we need to handle that. + private async getPaneIcon (pane, subject, context) { if (!pane) return undefined const icon = typeof pane.icon === 'function' - ? pane.icon(subject, context) + ? await pane.icon(subject, context) : pane.icon return icon } @@ -174,12 +192,21 @@ export default class FileExplorerProvider extends WebComponent { } private refreshFileExplorerContextValue () { + const store = this.context?.session.store as LiveStore + const deleteTargetUri = deriveDeleteTargetUri(store, this.subjectUri, this.pane?.mintClass, this.deleteTargetUri) + + this.isContainerResourceValue = deleteTargetUri + ? solidLogicSingleton.resource.isContainer(store.sym(deleteTargetUri)) + : solidLogicSingleton.resource.isContainer(store.sym(this.subjectUri as string)) + this.fileExplorerContextValue = createFileExplorerContextValue({ - store: this.context?.session.store as LiveStore, + store, subjectUri: this.subjectUri, pane: this.pane, soloPane: this.soloPane, onBack: this.onBack, + refresh: this.refresh, + deleteTargetUri, openPane: this.openPane, handleSharingClick: this.handleSharingClick, paneSupportsEditing: this.paneSupportsEditing, @@ -190,7 +217,6 @@ export default class FileExplorerProvider extends WebComponent { private async refreshMenuItems () { const store = this.context?.session.store as LiveStore if (!store || !this.subjectUri) return - const subject = store.sym(this.subjectUri) const menuItems = await this.getPaneItems(subject, this.context as DataBrowserContext, this.relevantPanes) @@ -227,9 +253,10 @@ export default class FileExplorerProvider extends WebComponent { changedProperties.has('pane') || changedProperties.has('soloPane') || changedProperties.has('onBack') || + changedProperties.has('refresh') || + changedProperties.has('deleteTargetUri') || changedProperties.has('openPane') || changedProperties.has('handleSharingClick') || - changedProperties.has('pane') || changedProperties.has('isDirty') ) { this.refreshFileExplorerContextValue() @@ -247,6 +274,7 @@ export default class FileExplorerProvider extends WebComponent { .paneIcon=${this.getPaneIcon(this.pane, subject, this.context as DataBrowserContext)} .menuItems=${this.menuItems} .paneSupportsEditing=${this.paneSupportsEditing} + .isContainerResource=${this.isContainerResourceValue} > ` : nothing} diff --git a/src/components/file-explorer-header/helper.ts b/src/components/file-explorer-header/helper.ts index 129c30bb..6d64dc4e 100644 --- a/src/components/file-explorer-header/helper.ts +++ b/src/components/file-explorer-header/helper.ts @@ -1,105 +1,38 @@ -// TODO: this code will need to be moved later -import { ACL_LINK } from 'solid-logic' -import { ns } from 'solid-ui' import type { LiveStore, NamedNode } from 'rdflib' -export type FileExplorerResourceMetadata = { - contentType: string | undefined - canEdit: boolean - isPublic: boolean - aclUri: string | undefined - eTag: string | undefined - modified: string | undefined -} - -function parseWacAllowHeader (headerValue: string | null | undefined) { - const permissions = new Map>() - if (!headerValue) return permissions - - for (const entry of headerValue.split(',')) { - const match = entry.trim().match(/^([A-Za-z]+)\s*=\s*"([^"]*)"$/) - if (match) { - const [, permissionGroup, accessModes] = match - const modes = accessModes.trim().split(/\s+/).filter(Boolean) - permissions.set(permissionGroup.toLowerCase(), new Set(modes.map(mode => mode.toLowerCase()))) - } +export function deriveDeleteTargetUri ( + store: LiveStore | undefined, + subjectUri: string | undefined, + mintClass: NamedNode | undefined, + explicitDeleteTargetUri: string | undefined +) { + if (explicitDeleteTargetUri) return explicitDeleteTargetUri + if (!subjectUri || !mintClass || !store) return undefined + + const subject = store.sym(subjectUri) + const typeUris = store.findTypeURIs(subject) + if (!typeUris[mintClass.uri]) return undefined + + if (subjectUri.endsWith('/index.ttl#this')) { + return subjectUri.slice(0, -'index.ttl#this'.length) } - return permissions -} - -function deriveAccessFlags (wacAllow: string | null | undefined) { - if (!wacAllow) { - return { canEdit: false, isPublic: false } + if (subjectUri.endsWith('/index.ttl')) { + return subjectUri.slice(0, -'index.ttl'.length) } - const permissions = parseWacAllowHeader(wacAllow) - const userModes = permissions.get('user') ?? new Set() - const publicModes = permissions.get('public') ?? new Set() - - return { - canEdit: userModes.has('write'), - isPublic: publicModes.has('read') || publicModes.has('write') - } + return undefined } -export function getResponseMetadata (store: LiveStore, subject: NamedNode, response: Response): FileExplorerResourceMetadata { - let contentType: string | undefined - let canEdit = false - let isPublic = false - let eTag: string | undefined - let modified: string | undefined - - if (response.headers && response.headers.get('content-type')) { - contentType = response.headers.get('content-type')?.split(';')[0] ?? undefined - const accessFlags = deriveAccessFlags(response.headers.get('wac-allow')) - - canEdit = accessFlags.canEdit - isPublic = accessFlags.isPublic - eTag = response.headers.get('etag') ?? undefined - modified = store.anyValue(subject as any, ns.dct('modified')) || store.anyValue(subject as any, ns.dc('modified')) || undefined - } else { - const reqs = store.each( - null, - store.sym('http://www.w3.org/2007/ont/link#requestedURI'), - subject - ) - reqs.forEach((req: any) => { - const responseNode = store.any( - req as any, - store.sym('http://www.w3.org/2007/ont/link#response') - ) - if (responseNode && responseNode.termType === 'NamedNode') { - contentType = store.anyValue(responseNode as any, ns.httph('content-type')) || undefined - const wacAllow = (store.anyValue(responseNode as any, ns.httph('wac-allow')) as string | undefined) || - (store.anyValue(responseNode as any, ns.httph('WAC-Allow')) as string | undefined) - const accessFlags = deriveAccessFlags(wacAllow) - canEdit = accessFlags.canEdit - isPublic = accessFlags.isPublic - eTag = store.anyValue(responseNode as any, ns.httph('etag')) || undefined - modified = store.anyValue(subject as any, ns.dct('modified')) || store.anyValue(subject as any, ns.dc('modified')) || undefined - } - }) - } - - const aclUri = store.any(subject, ACL_LINK)?.value || undefined - return { contentType, canEdit, isPublic, aclUri, eTag, modified } -} - -export async function fetchContentAndMetadata (store: LiveStore, subject: NamedNode): Promise<{ content: string, metadata: FileExplorerResourceMetadata }> { - const fetcher = store.fetcher - - const response = await fetcher.webOperation('GET', subject.uri) - const content = (response as Response & { responseText?: string }).responseText - - if (content === undefined) { - throw new Error('No text in response object!!') - } - - const metadata = getResponseMetadata(store, subject, response) - if (!metadata.contentType) { - throw new Error('No content-type available!') - } - - return { content, metadata } +export function deriveDeleteRefreshTargetUri ( + store: LiveStore | undefined, + subjectUri: string | undefined, + mintClass: NamedNode | undefined, + explicitDeleteTargetUri: string | undefined +) { + const deleteTargetUri = deriveDeleteTargetUri(store, subjectUri, mintClass, explicitDeleteTargetUri) + const targetUri = deleteTargetUri ?? subjectUri + if (!targetUri || !store) return undefined + + return store.sym(targetUri).dir()?.uri } diff --git a/src/components/file-explorer-header/types.ts b/src/components/file-explorer-header/types.ts index b3b3ecbd..b85bbfdc 100644 --- a/src/components/file-explorer-header/types.ts +++ b/src/components/file-explorer-header/types.ts @@ -1 +1,11 @@ export type PaneIcon = string | Promise | null | undefined + +export type FileExplorerHeaderMetadata = { + access: { + canEdit: boolean + canDelete?: boolean + isPublic: boolean + } + aclUri: string | undefined + modified: string | undefined +} diff --git a/src/components/resource-actions-menu/DeleteResourceDialog.styles.css b/src/components/resource-actions-menu/DeleteResourceDialog.styles.css new file mode 100644 index 00000000..55160345 --- /dev/null +++ b/src/components/resource-actions-menu/DeleteResourceDialog.styles.css @@ -0,0 +1,12 @@ +:host { + .resource-delete-dialog__emphasis { + font-weight: 600; + } + + .resource-delete-dialog__actions { + display: flex; + justify-content: flex-end; + gap: var(--spacing-sm, 0.75em); + width: 100%; + } +} diff --git a/src/components/resource-actions-menu/DeleteResourceDialog.ts b/src/components/resource-actions-menu/DeleteResourceDialog.ts index 42b1908f..adc69c8d 100644 --- a/src/components/resource-actions-menu/DeleteResourceDialog.ts +++ b/src/components/resource-actions-menu/DeleteResourceDialog.ts @@ -1,6 +1,10 @@ import { customElement, DialogComponent } from 'solid-ui' +import { utils } from 'solid-ui' import { html } from 'lit' import { property } from 'lit/decorators.js' +import { solidLogicSingleton } from 'solid-logic' +import type { NamedNode } from 'rdflib' +import styles from './DeleteResourceDialog.styles.css' import 'solid-ui/components/button' import 'solid-ui/components/dialog' @@ -9,26 +13,43 @@ import 'solid-ui/components/dialog-footer' @customElement('resource-delete-dialog') export default class DeleteResourceDialog extends DialogComponent { - @property({ type: String }) - accessor resourceName = 'this resource' + static styles = styles + + @property({ attribute: false }) + accessor resourceNode: NamedNode = undefined as unknown as NamedNode private cancel = () => this.close(false) private confirm = () => this.close(true) protected render () { + const resourceLogic = solidLogicSingleton.resource + const resourceNode = this.resourceNode + const hasResourceValue = typeof resourceNode?.value === 'string' && resourceNode.value.length > 0 + const resourceLabel = hasResourceValue ? utils.label(resourceNode) : 'this resource' + const isContainer = hasResourceValue ? resourceLogic.isContainer(resourceNode) : false + const numberOfContents = hasResourceValue && isContainer + ? html`${resourceLogic.getContainerMemberCount(resourceNode)}` + : null + const resourceName = html`${resourceLabel}` + const message = isContainer + ? html`Are you sure you want to permanently delete ${resourceName} container and the ${numberOfContents} resources inside?` + : html`Are you sure you want to permanently delete ${resourceName}?` + return html` - + -

Are you sure you want to permanently delete ${this.resourceName}?

+

${message}

- - Go back - - - Yes - +
+ + Cancel + + + Delete + +
` diff --git a/src/components/resource-actions-menu/ResourceActionsMenu.ts b/src/components/resource-actions-menu/ResourceActionsMenu.ts index 884b3004..67d03875 100644 --- a/src/components/resource-actions-menu/ResourceActionsMenu.ts +++ b/src/components/resource-actions-menu/ResourceActionsMenu.ts @@ -1,14 +1,16 @@ -import { showDialog, utils, WebComponent } from 'solid-ui' +import { showDialog, WebComponent, fileExplorerContext, type FileExplorerContext, log } from 'solid-ui' import { customElement, property } from 'lit/decorators.js' -import { html } from 'lit' +import { html, nothing } from 'lit' +import { solidLogicSingleton } from 'solid-logic' import 'solid-ui/components/button' import 'solid-ui/components/menu' import 'solid-ui/components/menu-item' import DeleteResourceDialog from './DeleteResourceDialog' import '~icons/lucide/ellipsis-vertical' import '~icons/lucide/trash-2' -import { LiveStore } from 'rdflib' -// TODO: Add Error Status section +import '~icons/lucide/share-2' +import { type LiveStore, type NamedNode } from 'rdflib' +import { consume } from '@lit/context' @customElement('resource-actions-menu') export default class ResourceActionsMenu extends WebComponent { @@ -18,70 +20,69 @@ export default class ResourceActionsMenu extends WebComponent { @property({ type: String }) accessor subjectUri: string | undefined + @property({ type: String }) + accessor deleteTargetUri: string | undefined + + @consume({ context: fileExplorerContext, subscribe: true }) + accessor fileExplorerContext: FileExplorerContext = undefined as unknown as FileExplorerContext + @property({ attribute: false }) accessor menuItems: Array<{ label: string, icon?: HTMLElement, action: (event: Event) => void }> = [] - private confirmDelete (resourceName: string) { - return new Promise(resolve => { - showDialog(DeleteResourceDialog, { - props: { resourceName }, - onClose: (result) => resolve(result === true) - }) - }) - } + @property({ type: Boolean }) + accessor showShareItem: boolean = false - /* private gotoParentFolder(resourceUri: string) { - const sourceContext = this.sourceContext - const outliner = sourceContext?.context?.getOutliner?.(sourceContext.context.dom) + @property({ type: Boolean }) + accessor canDelete: boolean = false - if (!outliner) return - - const parentFolderUri = this.getParentFolderUri(resourceUri) - ;(outliner as any).GotoSubject(sourceContext.context.session.store.sym(parentFolderUri), true, undefined, true, undefined) - } */ + private async confirmDelete (resourceNode: NamedNode) { + const store = this.store + if (!store) return false - private async deleteResourceIfPresent (store: LiveStore, uri: string) { try { - await store.fetcher.webOperation('DELETE', uri) - } catch (err: any) { - const status = err?.response?.status ?? err?.status - if (status === 404) return - throw err + await store.fetcher.load(resourceNode.doc()) + } catch (_error) { + // Best-effort load: if it fails we still show the generic delete prompt. } + + return new Promise(resolve => { + showDialog(DeleteResourceDialog, { + props: { + resourceNode + }, + onClose: (result) => resolve(result === true) + }) + }) } - // TODO: Below is for a file only. I need to move this to a function - // that function needs to check if it's a container and if so we can do the - // recursive delete function. - // dont' forget public and private type indexes. private async handleDelete (event: Event) { event.preventDefault() if (!this.store?.fetcher || !this.subjectUri) return const store = this.store - const resourceNode = store.sym(this.subjectUri) + const deleteTargetUri = this.deleteTargetUri ?? this.subjectUri + if (!deleteTargetUri) return - const confirmation = await this.confirmDelete(utils.label(resourceNode)) + const resourceNode = store.sym(deleteTargetUri) + + const confirmation = await this.confirmDelete(resourceNode) if (!confirmation) return try { - await this.deleteResourceIfPresent(store, resourceNode.value) - - const aclUri = this.subjectUri + '.acl' - if (aclUri) { - await this.deleteResourceIfPresent(store, aclUri) - store.removeDocument(store.sym(aclUri)) - } - - store.removeDocument(resourceNode) - // this.gotoParentFolder(resourceNode.value) - } catch (err) { - // error('Error deleting resource:', err) - // getStatusSection()?.showError('Failed to delete resource. Check console for details.') + await solidLogicSingleton.resource.deleteResourceAndTypeIndexIfExists(resourceNode, solidLogicSingleton.authn.currentUser()) + this.fileExplorerContext?.refresh?.() + } catch (caughtError) { + log.debug('Error deleting resource: ' + String(caughtError)) + // TODO: Add a status section here so delete errors can be shown to the user. } } + private handleShare = (event: Event) => { + event.preventDefault() + this.fileExplorerContext?.handleSharingClick?.() + } + render () { return html` @@ -94,7 +95,15 @@ export default class ResourceActionsMenu extends WebComponent { ${item.label} `)} - this.handleDelete(event)}> + ${this.showShareItem +? html` + this.handleShare(event)}> + + Share + + ` +: nothing} + this.handleDelete(event)}> Delete diff --git a/src/outline/manager.js b/src/outline/manager.js index 1207ad30..60f0813a 100644 --- a/src/outline/manager.js +++ b/src/outline/manager.js @@ -13,6 +13,7 @@ import { UserInput } from './userInput.js' import * as queryByExample from './queryByExample.js' import { loadContainerRepresentation } from '../utils/podUtils' import { isWebIdUri } from '../utils/webIdUtils' +import { deriveDeleteRefreshTargetUri } from '../components/file-explorer-header/helper' import '../components/file-explorer-header/FileExplorerProvider' export default function (context) { @@ -462,6 +463,17 @@ export default function (context) { provider.context = context provider.subjectUri = subject.uri provider.onBack = () => collapseMouseDownListener({ target: provider }) + provider.refresh = () => { + const deleteTargetUri = provider.fileExplorerContextValue?.deleteTargetUri + const refreshTargetUri = deriveDeleteRefreshTargetUri( + context.session.store, + subject.uri, + (provider.pane || requiredPane)?.mintClass, + deleteTargetUri + ) + if (!refreshTargetUri) return + openPaneInPlace(kb.sym(refreshTargetUri), paneRegistry.byName('folder')) + } const relevantPanes = options.hideList ? [] diff --git a/test/unit/components/delete-resource-dialog.test.ts b/test/unit/components/delete-resource-dialog.test.ts new file mode 100644 index 00000000..981b5f4c --- /dev/null +++ b/test/unit/components/delete-resource-dialog.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from 'vitest' + +import '../../../src/components/resource-actions-menu/DeleteResourceDialog' + +describe('DeleteResourceDialog', () => { + it('renders a generic delete prompt when resourceNode has no value', async () => { + const dialog = document.createElement('resource-delete-dialog') as any + dialog.resourceNode = {} as any + document.body.appendChild(dialog) + + await dialog.updateComplete + + expect(dialog.shadowRoot?.textContent).toContain('Are you sure you want to permanently delete this resource?') + }) +}) diff --git a/test/unit/components/file-explorer-provider.test.ts b/test/unit/components/file-explorer-provider.test.ts new file mode 100644 index 00000000..9ca6ac39 --- /dev/null +++ b/test/unit/components/file-explorer-provider.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from 'vitest' +import { store } from 'solid-logic' +import { sym } from 'rdflib' + +import '../../../src/components/file-explorer-header/FileExplorerProvider' + +import { + deriveDeleteRefreshTargetUri, + deriveDeleteTargetUri +} from '../../../src/components/file-explorer-header/helper' + +describe('FileExplorerProvider', () => { + it('derives deleteTargetUri only for panes that create index.ttl#this', async () => { + const mintClass = sym('https://example.com/ns#Notebook') + + const provider = document.createElement('file-explorer-provider') as any + provider.context = { session: { store } } + provider.subjectUri = 'https://example.com/workspace/index.ttl#this' + provider.pane = { + name: 'pad', + mintClass, + icon: undefined, + label: () => 'Pad', + render: () => document.createElement('div') + } as any + provider.relevantPanes = [] + + store.add( + sym('https://example.com/workspace/index.ttl#this'), + sym('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + sym('https://example.com/ns#Notebook'), + sym('https://example.com/workspace/index.ttl') + ) + + document.body.appendChild(provider) + + await provider.updateComplete + + expect(provider.fileExplorerContextValue.deleteTargetUri).toBe('https://example.com/workspace/') + expect(provider.isContainerResourceValue).toBe(true) + + provider.pane = { name: 'source' } as any + await provider.updateComplete + + expect(provider.fileExplorerContextValue.deleteTargetUri).toBeUndefined() + expect(provider.isContainerResourceValue).toBe(false) + }) + + it('returns the parent of the deleted container for delete refresh navigation', () => { + const mintClass = sym('https://example.com/ns#Notebook') + + store.add( + sym('https://example.com/containerSharon/containeraddressbook/index.ttl#this'), + sym('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + sym('https://example.com/ns#Notebook'), + sym('https://example.com/containerSharon/containeraddressbook/index.ttl') + ) + + const deleteTargetUri = deriveDeleteTargetUri( + store, + 'https://example.com/containerSharon/containeraddressbook/index.ttl#this', + mintClass, + undefined + ) + + expect(deleteTargetUri).toBe('https://example.com/containerSharon/containeraddressbook/') + expect( + deriveDeleteRefreshTargetUri( + store, + 'https://example.com/containerSharon/containeraddressbook/index.ttl#this', + mintClass, + undefined + ) + ).toBe('https://example.com/containerSharon/') + }) +}) diff --git a/test/unit/components/resource-actions-menu.test.ts b/test/unit/components/resource-actions-menu.test.ts new file mode 100644 index 00000000..e4fc7cca --- /dev/null +++ b/test/unit/components/resource-actions-menu.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it, vi } from 'vitest' +import { sym } from 'rdflib' +import * as UI from 'solid-ui' +import { solidLogicSingleton } from 'solid-logic' + +import '../../../src/components/resource-actions-menu/ResourceActionsMenu' + +describe('ResourceActionsMenu', () => { + it('deletes the container root when the selected resource is index.ttl#this', async () => { + const deleteResourceAndTypeIndexIfExists = vi + .spyOn(solidLogicSingleton.resource, 'deleteResourceAndTypeIndexIfExists') + .mockResolvedValue(undefined as any) + + const showDialogSpy = vi.spyOn(UI, 'showDialog').mockImplementation((_DialogComponent, config) => { + config?.onClose?.(true) + return document.createElement('div') as any + }) + + const onBack = vi.fn() + const refresh = vi.fn() + const menu = document.createElement('resource-actions-menu') as any + menu.store = { + fetcher: { + load: vi.fn().mockResolvedValue(undefined) + }, + sym + } + menu.subjectUri = 'https://example.com/workspace/index.ttl#this' + menu.deleteTargetUri = 'https://example.com/workspace/' + menu.fileExplorerContext = { onBack, refresh } + + await menu.handleDelete(new Event('click')) + + expect(showDialogSpy).toHaveBeenCalledTimes(1) + expect(deleteResourceAndTypeIndexIfExists).toHaveBeenCalledWith( + sym('https://example.com/workspace/'), + solidLogicSingleton.authn.currentUser() + ) + expect(refresh).toHaveBeenCalledTimes(1) + expect(onBack).not.toHaveBeenCalled() + }) +}) From 977024922c98ad8ee1f4f27b5199fc164f2bdd61 Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Tue, 11 Aug 2026 08:02:46 +1000 Subject: [PATCH 2/3] address PR comments --- package-lock.json | 4 +- .../FileExplorerHeader.ts | 13 +++-- .../FileExplorerProvider.ts | 4 +- src/components/file-explorer-header/helper.ts | 3 ++ .../DeleteResourceDialog.ts | 7 ++- .../ResourceActionsMenu.ts | 6 ++- src/outline/manager.js | 11 +++- .../components/file-explorer-header.test.ts | 50 +++++++++++++++++++ .../components/file-explorer-provider.test.ts | 4 +- .../components/resource-actions-menu.test.ts | 40 ++++++++++++++- 10 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 test/unit/components/file-explorer-header.test.ts diff --git a/package-lock.json b/package-lock.json index 99c6b360..079c8b1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "solid-panes", - "version": "4.4.2-7", + "version": "4.4.2-8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "solid-panes", - "version": "4.4.2-7", + "version": "4.4.2-8", "license": "MIT", "dependencies": { "@solid/better-simple-slideshow": "^0.1.0", diff --git a/src/components/file-explorer-header/FileExplorerHeader.ts b/src/components/file-explorer-header/FileExplorerHeader.ts index 82110e8e..cc1016d2 100644 --- a/src/components/file-explorer-header/FileExplorerHeader.ts +++ b/src/components/file-explorer-header/FileExplorerHeader.ts @@ -16,7 +16,7 @@ import { PaneIcon, type FileExplorerHeaderMetadata } from './types' export default class FileExplorerHeader extends WebComponent { static styles = styles - private _loadedMetadataForUri: string | undefined + private _loadedMetadataForTargetUri: string | undefined @consume({ context: fileExplorerContext, subscribe: true }) accessor fileExplorerContext: FileExplorerContext = undefined as unknown as FileExplorerContext @@ -54,16 +54,19 @@ export default class FileExplorerHeader extends WebComponent { } protected updated () { - if (this.fileExplorerContext?.store && this.fileExplorerContext.subjectUri && this._loadedMetadataForUri !== this.fileExplorerContext.subjectUri) { - this._loadedMetadataForUri = this.fileExplorerContext.subjectUri + const loadTargetUri = this.fileExplorerContext?.deleteTargetUri ?? this.fileExplorerContext?.subjectUri + if (this.fileExplorerContext?.store && loadTargetUri && this._loadedMetadataForTargetUri !== loadTargetUri) { + this._loadedMetadataForTargetUri = loadTargetUri this.loadResponseMetadata() } } private async loadResponseMetadata () { - if (!this.fileExplorerContext?.store || !this.fileExplorerContext.subjectUri) return + if (!this.fileExplorerContext?.store) return + + const subjectUri = this.fileExplorerContext.deleteTargetUri ?? this.fileExplorerContext.subjectUri + if (!subjectUri) return - const subjectUri = this.fileExplorerContext.subjectUri const defaultMetadata = this.getDefaultResponseMetadata() try { diff --git a/src/components/file-explorer-header/FileExplorerProvider.ts b/src/components/file-explorer-header/FileExplorerProvider.ts index 40773b5a..4a5d5b2e 100644 --- a/src/components/file-explorer-header/FileExplorerProvider.ts +++ b/src/components/file-explorer-header/FileExplorerProvider.ts @@ -193,7 +193,9 @@ export default class FileExplorerProvider extends WebComponent { private refreshFileExplorerContextValue () { const store = this.context?.session.store as LiveStore - const deleteTargetUri = deriveDeleteTargetUri(store, this.subjectUri, this.pane?.mintClass, this.deleteTargetUri) + const deleteTargetUri = + deriveDeleteTargetUri(store, this.subjectUri, this.pane?.mintClass, this.deleteTargetUri) ?? + this.fileExplorerContextValue?.deleteTargetUri this.isContainerResourceValue = deleteTargetUri ? solidLogicSingleton.resource.isContainer(store.sym(deleteTargetUri)) diff --git a/src/components/file-explorer-header/helper.ts b/src/components/file-explorer-header/helper.ts index 6d64dc4e..dbd4a8cf 100644 --- a/src/components/file-explorer-header/helper.ts +++ b/src/components/file-explorer-header/helper.ts @@ -1,5 +1,8 @@ import type { LiveStore, NamedNode } from 'rdflib' +/* Resources for panes that have an index.ttl file can be deleted from +the index file as well as from the container. When the index file (pane driver) +is deleted the container it's in should also be deleted. */ export function deriveDeleteTargetUri ( store: LiveStore | undefined, subjectUri: string | undefined, diff --git a/src/components/resource-actions-menu/DeleteResourceDialog.ts b/src/components/resource-actions-menu/DeleteResourceDialog.ts index adc69c8d..75e7c34d 100644 --- a/src/components/resource-actions-menu/DeleteResourceDialog.ts +++ b/src/components/resource-actions-menu/DeleteResourceDialog.ts @@ -16,7 +16,10 @@ export default class DeleteResourceDialog extends DialogComponent { static styles = styles @property({ attribute: false }) - accessor resourceNode: NamedNode = undefined as unknown as NamedNode + accessor resourceNode: NamedNode | undefined = undefined + + @property({ type: Boolean }) + accessor resourceDetailsLoaded: boolean = true private cancel = () => this.close(false) @@ -25,7 +28,7 @@ export default class DeleteResourceDialog extends DialogComponent { protected render () { const resourceLogic = solidLogicSingleton.resource const resourceNode = this.resourceNode - const hasResourceValue = typeof resourceNode?.value === 'string' && resourceNode.value.length > 0 + const hasResourceValue = this.resourceDetailsLoaded && typeof resourceNode?.value === 'string' && resourceNode.value.length > 0 const resourceLabel = hasResourceValue ? utils.label(resourceNode) : 'this resource' const isContainer = hasResourceValue ? resourceLogic.isContainer(resourceNode) : false const numberOfContents = hasResourceValue && isContainer diff --git a/src/components/resource-actions-menu/ResourceActionsMenu.ts b/src/components/resource-actions-menu/ResourceActionsMenu.ts index 67d03875..6db6d1b0 100644 --- a/src/components/resource-actions-menu/ResourceActionsMenu.ts +++ b/src/components/resource-actions-menu/ResourceActionsMenu.ts @@ -39,16 +39,18 @@ export default class ResourceActionsMenu extends WebComponent { const store = this.store if (!store) return false + let resourceDetailsLoaded = true try { await store.fetcher.load(resourceNode.doc()) } catch (_error) { - // Best-effort load: if it fails we still show the generic delete prompt. + resourceDetailsLoaded = false } return new Promise(resolve => { showDialog(DeleteResourceDialog, { props: { - resourceNode + resourceNode: resourceDetailsLoaded ? resourceNode : undefined, + resourceDetailsLoaded }, onClose: (result) => resolve(result === true) }) diff --git a/src/outline/manager.js b/src/outline/manager.js index 60f0813a..7156f5d1 100644 --- a/src/outline/manager.js +++ b/src/outline/manager.js @@ -472,7 +472,16 @@ export default function (context) { deleteTargetUri ) if (!refreshTargetUri) return - openPaneInPlace(kb.sym(refreshTargetUri), paneRegistry.byName('folder')) + const outlineView = getOutlineContainer() + dom.outline.GotoSubject( + kb.sym(refreshTargetUri), + true, + paneRegistry.byName('folder'), + true, + undefined, + outlineView, + false + ) } const relevantPanes = options.hideList diff --git a/test/unit/components/file-explorer-header.test.ts b/test/unit/components/file-explorer-header.test.ts new file mode 100644 index 00000000..e1436439 --- /dev/null +++ b/test/unit/components/file-explorer-header.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it, vi } from 'vitest' +import { sym } from 'rdflib' +import { html } from 'lit' +import { solidLogicSingleton } from 'solid-logic' + +import '../../../src/components/file-explorer-header/FileExplorerHeader' + +describe('FileExplorerHeader', () => { + it('reloads delete authorization when the effective delete target changes', async () => { + const fetchMetadataWithDelete = vi.spyOn(solidLogicSingleton.resource, 'fetchMetadataWithDelete').mockImplementation(async (resource: any) => { + return { + modified: undefined, + access: { + canEdit: false, + isPublic: false, + canDelete: resource?.value?.endsWith('/workspace/') ?? false + }, + aclUri: undefined + } as any + }) + + const header = document.createElement('file-explorer-header') as any + header.render = () => html`` + header.fileExplorerContext = { + store: {}, + subjectUri: 'https://example.com/workspace/index.ttl#this', + deleteTargetUri: undefined + } + + document.body.appendChild(header) + await header.updateComplete + await header.updateComplete + + expect(fetchMetadataWithDelete).toHaveBeenCalledWith(sym('https://example.com/workspace/index.ttl#this')) + expect(header.responseMetadata.access.canDelete).toBe(false) + + header.fileExplorerContext = { + store: {}, + subjectUri: 'https://example.com/workspace/index.ttl#this', + deleteTargetUri: 'https://example.com/workspace/' + } + header.requestUpdate() + + await header.updateComplete + await header.updateComplete + + expect(fetchMetadataWithDelete).toHaveBeenCalledWith(sym('https://example.com/workspace/')) + expect(header.responseMetadata.access.canDelete).toBe(true) + }) +}) diff --git a/test/unit/components/file-explorer-provider.test.ts b/test/unit/components/file-explorer-provider.test.ts index 9ca6ac39..e352c96d 100644 --- a/test/unit/components/file-explorer-provider.test.ts +++ b/test/unit/components/file-explorer-provider.test.ts @@ -42,8 +42,8 @@ describe('FileExplorerProvider', () => { provider.pane = { name: 'source' } as any await provider.updateComplete - expect(provider.fileExplorerContextValue.deleteTargetUri).toBeUndefined() - expect(provider.isContainerResourceValue).toBe(false) + expect(provider.fileExplorerContextValue.deleteTargetUri).toBe('https://example.com/workspace/') + expect(provider.isContainerResourceValue).toBe(true) }) it('returns the parent of the deleted container for delete refresh navigation', () => { diff --git a/test/unit/components/resource-actions-menu.test.ts b/test/unit/components/resource-actions-menu.test.ts index e4fc7cca..5d61b115 100644 --- a/test/unit/components/resource-actions-menu.test.ts +++ b/test/unit/components/resource-actions-menu.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { sym } from 'rdflib' import * as UI from 'solid-ui' import { solidLogicSingleton } from 'solid-logic' @@ -6,6 +6,10 @@ import { solidLogicSingleton } from 'solid-logic' import '../../../src/components/resource-actions-menu/ResourceActionsMenu' describe('ResourceActionsMenu', () => { + afterEach(() => { + vi.restoreAllMocks() + }) + it('deletes the container root when the selected resource is index.ttl#this', async () => { const deleteResourceAndTypeIndexIfExists = vi .spyOn(solidLogicSingleton.resource, 'deleteResourceAndTypeIndexIfExists') @@ -39,4 +43,38 @@ describe('ResourceActionsMenu', () => { expect(refresh).toHaveBeenCalledTimes(1) expect(onBack).not.toHaveBeenCalled() }) + + it('shows a generic delete prompt when the pre-delete load fails', async () => { + const deleteResourceAndTypeIndexIfExists = vi + .spyOn(solidLogicSingleton.resource, 'deleteResourceAndTypeIndexIfExists') + .mockResolvedValue(undefined as any) + + vi.spyOn(UI, 'showDialog').mockImplementation((_DialogComponent, config) => { + const props = config?.props as any + expect(props?.resourceNode).toBeUndefined() + expect(props?.resourceDetailsLoaded).toBe(false) + config?.onClose?.(true) + return document.createElement('div') as any + }) + + const refresh = vi.fn() + const menu = document.createElement('resource-actions-menu') as any + menu.store = { + fetcher: { + load: vi.fn().mockRejectedValue(new Error('load failed')) + }, + sym + } + menu.subjectUri = 'https://example.com/workspace/index.ttl#this' + menu.deleteTargetUri = 'https://example.com/workspace/' + menu.fileExplorerContext = { refresh } + + await menu.handleDelete(new Event('click')) + + expect(deleteResourceAndTypeIndexIfExists).toHaveBeenCalledWith( + sym('https://example.com/workspace/'), + solidLogicSingleton.authn.currentUser() + ) + expect(refresh).toHaveBeenCalledTimes(1) + }) }) From 4451f849eebe5985aecd3f7d35c53a96bf60ab39 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 11 Aug 2026 02:08:23 +0000 Subject: [PATCH 3/3] chore: update solidos dependencies (dev: solid-logic@4.0.8-3 solid-ui@3.1.3-17 pane-registry@3.1.2-2 activitystreams-pane@1.0.3-4 chat-pane@3.0.4-3 contacts-pane@3.2.1-6 folder-pane@3.1.1-3 issue-pane@3.0.3-1 meeting-pane@3.0.3-1 profile-pane@3.2.3-4 source-pane@3.1.1-7) (latest: rdflib@2.4.0) --- package-lock.json | 344 ++++++++++++++++++++++++---------------------- package.json | 8 +- 2 files changed, 183 insertions(+), 169 deletions(-) diff --git a/package-lock.json b/package-lock.json index 079c8b1d..f05d30a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@solid/better-simple-slideshow": "^0.1.0", "activitystreams-pane": "1.0.3-4", "chat-pane": "3.0.4-3", - "contacts-pane": "3.2.1-5", + "contacts-pane": "3.2.1-6", "dompurify": "^3.4.4", "folder-pane": "3.1.1-3", "issue-pane": "3.0.3-1", @@ -23,10 +23,10 @@ "pane-registry": "3.1.2-2", "profile-pane": "3.2.3-4", "rdflib": "2.4.0", - "solid-logic": "4.0.8-2", + "solid-logic": "4.0.8-3", "solid-namespace": "^0.5.4", - "solid-ui": "3.1.3-15", - "source-pane": "3.1.1-6" + "solid-ui": "3.1.3-17", + "source-pane": "3.1.1-7" }, "devDependencies": { "@iconify/json": "2.2.494", @@ -2034,6 +2034,21 @@ "@lezer/json": "^1.0.0" } }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.2.tgz", + "integrity": "sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, "node_modules/@codemirror/lang-xml": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", @@ -2082,17 +2097,6 @@ "crelt": "^1.0.5" } }, - "node_modules/@codemirror/search": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.1.tgz", - "integrity": "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.37.0", - "crelt": "^1.0.5" - } - }, "node_modules/@codemirror/state": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.1.tgz", @@ -2312,13 +2316,13 @@ } }, "node_modules/@digitalbazaar/http-client": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-4.3.0.tgz", - "integrity": "sha512-6lMpxpt9BOmqHKGs9Xm6DP4LlZTBFer/ZjHvP3FcW3IaUWYIWC7dw5RFZnvw4fP57kAVcm1dp3IF+Y50qhBvAw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-4.4.0.tgz", + "integrity": "sha512-ODhCGmElUPmR3IR+KZmBNkRAFyjJ01rxvk2E+/qQ2h2EGPJH5k6bz3N24ympGc5+i4YCGk/ipIpmkwc0+iSmRg==", "license": "BSD-3-Clause", "dependencies": { - "ky": "^1.14.2", - "undici": "^6.23.0" + "ky": "^1.14.3", + "undici": "^6.28.0" }, "engines": { "node": ">=18.0" @@ -2827,6 +2831,16 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@lezer/markdown": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.7.2.tgz", + "integrity": "sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, "node_modules/@lezer/xml": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", @@ -2969,12 +2983,12 @@ "license": "MIT" }, "node_modules/@noble/curves": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", - "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz", + "integrity": "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg==", "license": "MIT", "dependencies": { - "@noble/hashes": "2.2.0" + "@noble/hashes": "2.3.0" }, "engines": { "node": ">= 20.19.0" @@ -2984,9 +2998,9 @@ } }, "node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", + "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -3529,17 +3543,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", - "integrity": "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", + "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/type-utils": "8.66.0", - "@typescript-eslint/utils": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/type-utils": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -3552,7 +3566,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.66.0", + "@typescript-eslint/parser": "^8.67.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -3568,15 +3582,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", - "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3" }, "engines": { @@ -3592,13 +3606,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", - "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.66.0", - "@typescript-eslint/types": "^8.66.0", + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", "debug": "^4.4.3" }, "engines": { @@ -3613,13 +3627,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", - "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0" + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3630,9 +3644,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", - "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3646,15 +3660,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz", - "integrity": "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/utils": "8.66.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3671,9 +3685,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", - "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", + "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3684,15 +3698,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", - "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.66.0", - "@typescript-eslint/tsconfig-utils": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3711,16 +3725,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", - "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0" + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3735,12 +3749,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", - "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/types": "8.67.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -4421,9 +4435,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.11.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", - "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", + "version": "2.11.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz", + "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4468,9 +4482,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", - "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "dev": true, "funding": [ { @@ -4488,11 +4502,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.44", - "caniuse-lite": "^1.0.30001806", - "electron-to-chromium": "^1.5.393", - "node-releases": "^2.0.51", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" @@ -4591,9 +4605,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001806", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", - "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", "dev": true, "funding": [ { @@ -4736,21 +4750,6 @@ "wrap-ansi": "^6.2.0" } }, - "node_modules/codemirror": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", - "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/commands": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/search": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4799,9 +4798,9 @@ "license": "MIT" }, "node_modules/contacts-pane": { - "version": "3.2.1-5", - "resolved": "https://registry.npmjs.org/contacts-pane/-/contacts-pane-3.2.1-5.tgz", - "integrity": "sha512-EuEeD8J5dtSkODaOEjIbLtEf4wurE4M5XHUbPgh2ZKFzCtYRxBwb1J5W+rnAfA1lQBn3EQ4fDe/b1RfyPVjbjA==", + "version": "3.2.1-6", + "resolved": "https://registry.npmjs.org/contacts-pane/-/contacts-pane-3.2.1-6.tgz", + "integrity": "sha512-Mf8OCx5Jp4bgTOI0QdDp/Ecs0Pl2SWqriqM/HSnVJ1Q69WOi081pBymR6shL4cP0hv2SHEBy7sjABTXEqjqMrg==", "license": "MIT", "dependencies": { "@lit/context": "^1.1.6", @@ -5223,9 +5222,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.401", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.401.tgz", - "integrity": "sha512-H6ViHN68nGYlChEvlIU67fn8O2/tpbWQPwck98yaJmh+08LSvHiydzDQ6oXNccLU3kNRVIRS9A4mA7CG+i6fLQ==", + "version": "1.5.404", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.404.tgz", + "integrity": "sha512-3WJtd7/lVq2Jnuz6wed1l9+1ZD2u2Tet1/1NBc4Iedkmgbu+I7YuAqdAQ8T+VZtnwysMsAf3IqSq9D1gyZjA2g==", "dev": true, "license": "ISC" }, @@ -6071,6 +6070,21 @@ "vitest-axe": "^0.1.0" } }, + "node_modules/folder-pane/node_modules/solid-logic": { + "version": "4.0.8-2", + "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-4.0.8-2.tgz", + "integrity": "sha512-1/QFgchqCSkmIKvN47t6/OuBays1Yk6MoVtENqesgcpL5t2Q9hzqpQA6ToZNvzDu7Ep6Gs50rJSB1jJJsFi23Q==", + "license": "MIT", + "dependencies": { + "@uvdsl/solid-oidc-client-browser": "^0.2.3", + "solid-namespace": "^0.5.4", + "vite": "^8.0.16", + "vitest": "^4.1.9" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/folder-pane/node_modules/solid-ui": { "version": "3.1.3-14", "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-3.1.3-14.tgz", @@ -8595,9 +8609,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.52", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.52.tgz", - "integrity": "sha512-MRlTqhAfoMx/4mhEbPo3Hi02g9LJZaJkka69V6h67Cb1gjrAG0jsTE4CZX1eptNx+VCAwJmfpnDIF4P0Nh1A7A==", + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", "dev": true, "license": "MIT", "engines": { @@ -9190,9 +9204,9 @@ } }, "node_modules/postcss": { - "version": "8.5.25", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", - "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "funding": [ { "type": "opencollective", @@ -9209,7 +9223,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -9247,9 +9261,9 @@ } }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.17", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", - "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -10182,9 +10196,9 @@ "license": "MIT" }, "node_modules/solid-logic": { - "version": "4.0.8-2", - "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-4.0.8-2.tgz", - "integrity": "sha512-1/QFgchqCSkmIKvN47t6/OuBays1Yk6MoVtENqesgcpL5t2Q9hzqpQA6ToZNvzDu7Ep6Gs50rJSB1jJJsFi23Q==", + "version": "4.0.8-3", + "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-4.0.8-3.tgz", + "integrity": "sha512-0HKcZJb5ZF2MvFzOid26LMilgL6jD1zaRpix5nb2fB1h3H9WB0kquyD/297L6jIiUTIYSMlVb7ivpvkiVNTTNQ==", "license": "MIT", "dependencies": { "@uvdsl/solid-oidc-client-browser": "^0.2.3", @@ -10203,22 +10217,34 @@ "license": "MIT" }, "node_modules/solid-ui": { - "version": "3.1.3-15", - "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-3.1.3-15.tgz", - "integrity": "sha512-qX/IabG/Wets1w7ZQ+wLg9HIhBz3n8BTr36hC0whwfpjFBKe/62fow9CkrIVbRcSLz+fZ9+44SU5aUo4mFAoqg==", + "version": "3.1.3-17", + "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-3.1.3-17.tgz", + "integrity": "sha512-XVnz8u3lhW0AhmywMlEOif23lYkQcCmWiL5NUf8A5kmz3Ixw3E/qGiqmPpJ8VAOPZhEsj8+G7u+Y6pUDmhrLeA==", "license": "MIT", "dependencies": { "@awesome.me/webawesome": "^3.9.0", + "@codemirror/commands": "^6.10.0", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.11", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-markdown": "^6.3.1", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/language": "^6.12.3", + "@codemirror/legacy-modes": "^6.5.3", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.43.0", "@lit/context": "^1.1.6", "@lit/task": "^1.0.3", "@noble/curves": "^2.2.0", "@noble/hashes": "^2.2.0", + "@uiw/codemirror-theme-vscode": "^4.25.10", "escape-html": "^1.0.3", "lit": "^3.3.3", "mime-types": "^3.0.2", "pane-registry": "3.1.2-2", "rdflib": "2.4.0", - "solid-logic": "4.0.8-2", + "solid-logic": "4.0.8-3", "solid-namespace": "^0.5.4", "uuid": "^14.0.0" }, @@ -10286,30 +10312,18 @@ } }, "node_modules/source-pane": { - "version": "3.1.1-6", - "resolved": "https://registry.npmjs.org/source-pane/-/source-pane-3.1.1-6.tgz", - "integrity": "sha512-hus/mBSt2fo8nzxJwHwjMLHFKBivTjsF9MknpL0dIsRae35bIbrVrYs79icp7b419h4PfWGa337eJaA1GOrfWQ==", + "version": "3.1.1-7", + "resolved": "https://registry.npmjs.org/source-pane/-/source-pane-3.1.1-7.tgz", + "integrity": "sha512-fssbKBXlm16/YA8S/5W/YNUZILlR54DeC71CIr5j0FJx/R8Ts2b7ClMtdpFput39vW7+WCpSH1NkBW+k97157w==", "license": "MIT", "dependencies": { - "@codemirror/commands": "^6.10.0", - "@codemirror/lang-css": "^6.3.1", - "@codemirror/lang-html": "^6.4.11", - "@codemirror/lang-javascript": "^6.2.5", - "@codemirror/lang-json": "^6.0.2", - "@codemirror/lang-xml": "^6.1.0", - "@codemirror/language": "^6.12.3", - "@codemirror/legacy-modes": "^6.5.3", - "@codemirror/state": "^6.6.0", - "@codemirror/view": "^6.43.0", "@lit/context": "^1.1.6", "@typescript-eslint/parser": "^8.63.0", - "@uiw/codemirror-theme-vscode": "^4.25.10", - "codemirror": "^6.0.2", "lit": "^3.3.3", "pane-registry": "3.1.2-2", "rdflib": "2.4.0", - "solid-logic": "4.0.8-2", - "solid-ui": "3.1.3-15", + "solid-logic": "4.0.8-3", + "solid-ui": "3.1.3-17", "vite": "^8.0.16", "vitest": "^4.0.18", "vitest-axe": "^0.1.0" @@ -10875,16 +10889,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.66.0.tgz", - "integrity": "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==", + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", + "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.66.0", - "@typescript-eslint/parser": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/utils": "8.66.0" + "@typescript-eslint/eslint-plugin": "8.67.0", + "@typescript-eslint/parser": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -11142,9 +11156,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", "dev": true, "funding": [ { @@ -11229,15 +11243,15 @@ } }, "node_modules/vite": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", - "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", - "postcss": "^8.5.23", - "rolldown": "~1.2.0", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "bin": { diff --git a/package.json b/package.json index d11eadf6..41e8bc41 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@solid/better-simple-slideshow": "^0.1.0", "activitystreams-pane": "1.0.3-4", "chat-pane": "3.0.4-3", - "contacts-pane": "3.2.1-5", + "contacts-pane": "3.2.1-6", "dompurify": "^3.4.4", "folder-pane": "3.1.1-3", "issue-pane": "3.0.3-1", @@ -70,10 +70,10 @@ "pane-registry": "3.1.2-2", "profile-pane": "3.2.3-4", "rdflib": "2.4.0", - "solid-logic": "4.0.8-2", + "solid-logic": "4.0.8-3", "solid-namespace": "^0.5.4", - "solid-ui": "3.1.3-15", - "source-pane": "3.1.1-6" + "solid-ui": "3.1.3-17", + "source-pane": "3.1.1-7" }, "overrides": { "rdflib": "$rdflib"