From b4084d9f1c890b11f676b7097ec17934e4b4243f Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Sat, 7 Nov 2026 15:06:48 +1100 Subject: [PATCH 1/3] Fix delete it was not working, also use new dialog Prompt: Use deleteRecursive from solid-logic, create a resource action dialog and use the solid-ui-dialog, use this in internal pane for confirmation and for messaging. Co-authored-by: GPT-5.6 Luna --- .../ResourceActionDialog.styles.css | 24 +++ .../ResourceActionDialog.ts | 139 +++++++++++++++ src/internal/internalPane.ts | 127 ++++++-------- .../components/resourceActionDialog.test.ts | 67 ++++++++ test/unit/internalPane.test.ts | 161 ++++++++++++++++++ 5 files changed, 445 insertions(+), 73 deletions(-) create mode 100644 src/components/resource-action-dialog/ResourceActionDialog.styles.css create mode 100644 src/components/resource-action-dialog/ResourceActionDialog.ts create mode 100644 test/unit/components/resourceActionDialog.test.ts create mode 100644 test/unit/internalPane.test.ts diff --git a/src/components/resource-action-dialog/ResourceActionDialog.styles.css b/src/components/resource-action-dialog/ResourceActionDialog.styles.css new file mode 100644 index 00000000..6c8fe64f --- /dev/null +++ b/src/components/resource-action-dialog/ResourceActionDialog.styles.css @@ -0,0 +1,24 @@ +solid-ui-dialog-footer { + justify-content: flex-end; + flex-wrap: wrap; +} + +solid-ui-dialog-header h2 { + margin: 0; + font: inherit; +} + +solid-ui-dialog-content p { + overflow-wrap: anywhere; +} + +solid-ui-button { + min-width: 24px; + min-height: 24px; +} + +solid-ui-button:focus-within { + outline: 2px solid currentColor; + outline-offset: 2px; + border-radius: 5px; +} diff --git a/src/components/resource-action-dialog/ResourceActionDialog.ts b/src/components/resource-action-dialog/ResourceActionDialog.ts new file mode 100644 index 00000000..d6625269 --- /dev/null +++ b/src/components/resource-action-dialog/ResourceActionDialog.ts @@ -0,0 +1,139 @@ +import { html } from 'lit' +import { property } from 'lit/decorators.js' +import { customElement, WebComponent } from 'solid-ui' +import 'solid-ui/components/button' +import 'solid-ui/components/dialog' +import 'solid-ui/components/dialog-content' +import 'solid-ui/components/dialog-footer' +import 'solid-ui/components/dialog-header' +import '~icons/lucide/x' +import styles from './ResourceActionDialog.styles.css' + +@customElement('solid-panes-resource-action-dialog') +export default class ResourceActionDialog extends WebComponent { + static styles = styles + + @property({ type: String }) + accessor title = '' + + @property({ type: String }) + accessor message = '' + + @property({ type: Boolean }) + accessor confirmation = false + + private result: ((value: boolean) => void) | null = null + private returnFocus: HTMLElement | null = null + + public open (): Promise { + let activeElement = this.ownerDocument.activeElement + while (activeElement?.shadowRoot?.activeElement) { + activeElement = activeElement.shadowRoot.activeElement + } + this.returnFocus = activeElement instanceof HTMLElement ? activeElement : null + return new Promise(resolve => { + this.result = resolve + }) + } + + private closeDialog (result: boolean) { + const dialog = this.shadowRoot?.querySelector('solid-ui-dialog') + const nativeDialog = dialog?.shadowRoot?.querySelector('dialog') + this.result?.(result) + this.result = null + if (nativeDialog?.open) { + nativeDialog.close() + } + this.remove() + } + + disconnectedCallback () { + super.disconnectedCallback() + this.result?.(false) + this.result = null + if (this.returnFocus?.isConnected) { + this.returnFocus.focus() + } else { + const main = this.ownerDocument.querySelector('main') + if (main) { + const previousTabIndex = main.getAttribute('tabindex') + main.tabIndex = -1 + main.focus() + if (previousTabIndex === null) main.removeAttribute('tabindex') + else main.setAttribute('tabindex', previousTabIndex) + } + } + this.returnFocus = null + } + + protected async firstUpdated () { + super.firstUpdated() + const dialog = this.shadowRoot?.querySelector('solid-ui-dialog') + await dialog?.updateComplete + const nativeDialog = dialog?.shadowRoot?.querySelector('dialog') + if (!nativeDialog) throw new Error('Internal pane dialog did not render a native dialog') + nativeDialog.setAttribute('aria-label', this.title) + nativeDialog.setAttribute('aria-description', this.message) + nativeDialog.addEventListener('cancel', event => { + event.preventDefault() + this.closeDialog(false) + }) + nativeDialog.addEventListener('close', () => { + if (this.isConnected) this.closeDialog(false) + }) + const initialAction = this.shadowRoot?.querySelector('[data-initial-action]') + await initialAction?.updateComplete + initialAction?.shadowRoot?.querySelector('button')?.focus() + } + + private handleKeyDown (event: KeyboardEvent) { + if (event.key !== 'Tab') return + const buttons = Array.from(this.shadowRoot?.querySelectorAll('solid-ui-button') ?? []) + .map(component => component.shadowRoot?.querySelector('button')) + .filter((button): button is HTMLButtonElement => !!button && !button.disabled) + const focusedIndex = buttons.findIndex(button => { + const root = button.getRootNode() + return root instanceof ShadowRoot && root.activeElement === button + }) + if (event.shiftKey && focusedIndex === 0) { + event.preventDefault() + buttons.at(-1)?.focus() + } else if (!event.shiftKey && focusedIndex === buttons.length - 1) { + event.preventDefault() + buttons[0]?.focus() + } + } + + protected render () { + return html` + + +

${this.title}

+ this.closeDialog(false)}> + Close dialog + + +
+ +

${this.message}

+
+ + ${this.confirmation + ? html` + this.closeDialog(false)}> + Cancel + + this.closeDialog(true)}> + Delete + + ` + : html` + this.closeDialog(false)}> + OK + + `} + +
+ ` + } +} diff --git a/src/internal/internalPane.ts b/src/internal/internalPane.ts index fdc8691e..4cb898e1 100644 --- a/src/internal/internalPane.ts +++ b/src/internal/internalPane.ts @@ -3,12 +3,13 @@ ** This outline pane contains the properties which are ** internal to the user's interaction with the web, and are not normally displayed */ -/* global alert confirm */ - +import { solidLogicSingleton } from 'solid-logic' import { icons, ns, widgets } from 'solid-ui' -import { BlankNode, IndexedFormula, literal, NamedNode, st, sym, Variable, Store } from 'rdflib' +import { BlankNode, literal, NamedNode, st, sym, Variable, Store } from 'rdflib' import { PaneDefinition } from 'pane-registry' import './internalPane.css' +import '../components/resource-action-dialog/ResourceActionDialog' +import type ResourceActionDialog from '../components/resource-action-dialog/ResourceActionDialog' const pane: PaneDefinition = { icon: icons.originalIconBase + 'tango/22-emblem-system.png', @@ -39,45 +40,23 @@ const pane: PaneDefinition = { const div = dom.createElement('div') div.classList.add('internalPane', 'internalPaneContent') - function deleteRecursive (kb: IndexedFormula, folder: NamedNode) { - const fetcher = (kb as any).fetcher - if (!fetcher) { - console.error('No fetcher available') - return - } - return new Promise(function (resolve, reject) { - fetcher.load(folder).then(function () { - const promises = kb.each(folder, ns.ldp('contains')).map(file => { - if (kb.holds(file, ns.rdf('type'), ns.ldp('BasicContainer'))) { - return deleteRecursive(kb, file as NamedNode) - } else { - console.log('deleteRecursive leaf file: ' + file) - return fetcher.webOperation('DELETE', (file as NamedNode).uri) - } - }) - Promise.all(promises).then( - () => { - console.log('deleteRecursive empty folder: ' + folder) - fetcher - .webOperation('DELETE', folder.uri) - .then(() => { - console.log('Deleted Ok: ' + folder) - resolve(undefined) - }) - .catch((err: string) => { - const str = 'Unable to delete ' + folder + ': ' + err - console.log(str) - reject(new Error(str)) - }) - resolve(undefined) - }, - err => { - alert(err) - reject(err) - } - ) - }) - }) + function showMessage (title: string, message: string) { + const dialog = dom.createElement('solid-panes-resource-action-dialog') as ResourceActionDialog + dialog.title = title + dialog.message = message + dialog.confirmation = false + dialog.open() + dom.body?.appendChild(dialog) + } + + function confirmDeletion (message: string): Promise { + const dialog = dom.createElement('solid-panes-resource-action-dialog') as ResourceActionDialog + dialog.title = 'Delete resource' + dialog.message = message + dialog.confirmation = true + const result = dialog.open() + dom.body?.appendChild(dialog) + return result } const isDocument = subject.uri && !subject.uri.includes('#') @@ -92,40 +71,41 @@ const pane: PaneDefinition = { store.holds(subject, ns.rdf('type'), ns.ldp('Container')) const noun = isFolder ? 'folder' : 'file' if (!isProtectedUri(subject)) { - console.log(subject) - const deleteButton = widgets.deleteButtonWithCheck( + let deletionPending = false + const deleteButton = widgets.button( dom, - deleteCell, - noun, - function () { - if ( - !confirm( - `Are you sure you want to delete ${subject}? This cannot be undone.` - ) - ) { - return - } - // @@ TODO Remove casing of store.fetcher - if (!store.fetcher) { - throw new Error('Store has no fetcher') - } - const promise = isFolder - ? (deleteRecursive(store, subject) || Promise.resolve()) - : store.fetcher.webOperation('DELETE', subject.uri) || Promise.resolve() - promise - .then(() => { - const str = 'Deleted: ' + subject - console.log(str) - }) - .catch((err: any) => { - const str = 'Unable to delete ' + subject + ': ' + err - console.log(str) - alert(str) + icons.iconBase + 'noun_2188_red.svg', + 'Remove this ' + noun, + () => { + if (deletionPending) return + return confirmDeletion(`Are you sure you want to delete ${subject}? This cannot be undone.`) + .then(confirmed => { + if (!confirmed) return + deletionPending = true + deleteButton.disabled = true + const resourceLogic = context.session.logic?.resource || solidLogicSingleton.resource + return resourceLogic.recursiveDelete(subject) + .then(() => { + const str = 'Deleted: ' + subject + console.log(str) + showMessage('Resource deleted', str) + dom.dispatchEvent(new CustomEvent('solid-resource-deleted', { + detail: { resource: subject } + })) + }) + .catch((err: unknown) => { + const str = 'Unable to delete ' + subject + ': ' + (err instanceof Error ? err.message : String(err)) + console.log(str) + showMessage('Unable to delete resource', str) + }) + .finally(() => { + deletionPending = false + deleteButton.disabled = false + }) }) } ) - deleteButton.removeAttribute('class') // Remove hover hide deleteButton.classList.add('internalPaneDeleteButton') deleteCell.appendChild(deleteButton) } @@ -152,12 +132,13 @@ const pane: PaneDefinition = { str = 'Error refreshing: ' + subject + ': ' + errm } console.log(str) - alert(str) + showMessage(ok ? 'Resource refreshed' : 'Unable to refresh resource', str) }) }) } - let plist = store.statementsMatching(subject) + // rdflib returns its live subject index; display-only statements must not enter it. + let plist = store.statementsMatching(subject).slice() let docURI = '' if (!store.fetcher) { throw new Error('Store has no fetcher') diff --git a/test/unit/components/resourceActionDialog.test.ts b/test/unit/components/resourceActionDialog.test.ts new file mode 100644 index 00000000..c32cf006 --- /dev/null +++ b/test/unit/components/resourceActionDialog.test.ts @@ -0,0 +1,67 @@ +import { afterEach, describe, expect, it } from 'vitest' +import ResourceActionDialog from '../../../src/components/resource-action-dialog/ResourceActionDialog' + +afterEach(() => { + document.body.replaceChildren() +}) + +async function openDialog (confirmation: boolean) { + const trigger = document.createElement('button') + document.body.appendChild(trigger) + trigger.focus() + const dialog = new ResourceActionDialog() + dialog.title = confirmation ? 'Delete resource' : 'Resource deleted' + dialog.message = 'Test resource message' + dialog.confirmation = confirmation + const result = dialog.open() + document.body.appendChild(dialog) + await dialog.updateComplete + const wrapper = dialog.shadowRoot!.querySelector('solid-ui-dialog')! + await (wrapper as HTMLElement & { updateComplete: Promise }).updateComplete + const action = dialog.shadowRoot!.querySelector('[data-initial-action]')! + await (action as HTMLElement & { updateComplete: Promise }).updateComplete + const nativeDialog = wrapper.shadowRoot!.querySelector('dialog')! + nativeDialog.close = () => { + nativeDialog.removeAttribute('open') + nativeDialog.dispatchEvent(new Event('close')) + } + return { dialog, result, trigger, nativeDialog } +} + +describe('internal pane dialog', () => { + it('names the dialog, focuses Cancel, and resolves native cancellation without deletion', async () => { + const { dialog, result, trigger, nativeDialog } = await openDialog(true) + expect(nativeDialog).toHaveAttribute('aria-label', 'Delete resource') + expect(nativeDialog).toHaveAttribute('aria-description', 'Test resource message') + const action = dialog.shadowRoot!.querySelector('[data-initial-action]')! + expect(action.textContent?.trim()).toBe('Cancel') + expect(action.shadowRoot!.activeElement).toBe(action.shadowRoot!.querySelector('button')) + + nativeDialog.dispatchEvent(new Event('cancel', { cancelable: true })) + await expect(result).resolves.toBe(false) + expect(dialog.isConnected).toBe(false) + expect(trigger).toHaveFocus() + }) + + it('focuses OK for informational messages and resolves when removed externally', async () => { + const { dialog, result, trigger } = await openDialog(false) + const action = dialog.shadowRoot!.querySelector('[data-initial-action]')! + expect(action.textContent?.trim()).toBe('OK') + expect(action.shadowRoot!.activeElement).toBe(action.shadowRoot!.querySelector('button')) + dialog.remove() + await expect(result).resolves.toBe(false) + expect(trigger).toHaveFocus() + }) + + it('wraps keyboard focus between the first and last actions', async () => { + const { dialog } = await openDialog(true) + const actions = Array.from(dialog.shadowRoot!.querySelectorAll('solid-ui-button')) + const first = actions[0].shadowRoot!.querySelector('button')! + const last = actions.at(-1)!.shadowRoot!.querySelector('button')! + last.focus() + last.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true, composed: true, cancelable: true })) + expect(actions[0].shadowRoot!.activeElement).toBe(first) + first.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true, bubbles: true, composed: true, cancelable: true })) + expect(actions.at(-1)!.shadowRoot!.activeElement).toBe(last) + }) +}) diff --git a/test/unit/internalPane.test.ts b/test/unit/internalPane.test.ts new file mode 100644 index 00000000..16f1d7d3 --- /dev/null +++ b/test/unit/internalPane.test.ts @@ -0,0 +1,161 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { Fetcher, graph, literal, sym, UpdateManager } from 'rdflib' +import type { DataBrowserContext } from 'pane-registry' +import internalPane from '../../src/internal/internalPane' + +vi.mock('../../src/components/resource-action-dialog/ResourceActionDialog', () => ({})) + +class TestResourceActionDialog extends HTMLElement { + static confirmed = true + + title = '' + message = '' + confirmation = false + + open () { + return Promise.resolve(this.confirmation ? TestResourceActionDialog.confirmed : false) + } +} + +if (!customElements.get('solid-panes-resource-action-dialog')) { + customElements.define('solid-panes-resource-action-dialog', TestResourceActionDialog) +} + +beforeEach(() => { + TestResourceActionDialog.confirmed = true + document.body.replaceChildren() +}) + +async function flushPromises () { + await new Promise(resolve => setTimeout(resolve, 0)) +} + +describe('Under The Hood RDF bookkeeping', () => { + it('keeps display-only statements out of the live store so the document can be unloaded', () => { + const store = graph() + const fetcher = new Fetcher(store) + store.updater = new UpdateManager(store) + const subject = sym('https://example.test/file.ttl') + const predicate = sym('https://example.test/title') + store.add(subject, predicate, literal('A real file'), subject) + const originalStatements = store.statementsMatching(subject).slice() + const appendPropertyTRs = vi.fn() + const context = { + dom: document, + session: { + store, + paneRegistry: { byName: () => internalPane } + }, + getOutliner: () => ({ appendPropertyTRs }) + } as unknown as DataBrowserContext + + internalPane.render(subject, context) + internalPane.render(subject, context) + + expect(appendPropertyTRs.mock.calls[0][1]).toEqual(expect.arrayContaining([ + expect.objectContaining({ predicate: sym('http://www.w3.org/2007/ont/link#uri') }) + ])) + expect(store.statementsMatching(subject)).toEqual(originalStatements) + expect(store.statements).toEqual(originalStatements) + expect(() => fetcher.unload(subject)).not.toThrow() + expect(store.statementsMatching(subject)).toEqual([]) + expect(store.statements).toEqual([]) + }) +}) + +describe('Under The Hood deletion', () => { + function renderDeletionPane () { + const store = graph() + store.fetcher = new Fetcher(store) + store.updater = new UpdateManager(store) + const subject = sym('https://example.test/file.ttl') + const recursiveDelete = vi.fn(() => Promise.resolve()) + const appendPropertyTRs = vi.fn() + const context = { + dom: document, + session: { + store, + logic: { resource: { recursiveDelete } }, + paneRegistry: { byName: () => internalPane } + }, + getOutliner: () => ({ appendPropertyTRs }) + } as unknown as DataBrowserContext + + const pane = internalPane.render(subject, context) + document.body.appendChild(pane) + const deleteButton = pane.querySelector('.internalPaneDeleteButton') as HTMLButtonElement + return { subject, recursiveDelete, deleteButton } + } + + it('deletes the resource once and emits the success event after confirmation', async () => { + const { subject, recursiveDelete, deleteButton } = renderDeletionPane() + const deleted = vi.fn() + document.addEventListener('solid-resource-deleted', deleted) + + deleteButton.click() + await flushPromises() + expect(recursiveDelete).toHaveBeenCalledOnce() + expect(recursiveDelete).toHaveBeenCalledWith(subject) + expect(deleted).toHaveBeenCalledOnce() + expect(deleted).toHaveBeenCalledWith(expect.objectContaining({ + detail: { resource: subject } + })) + expect(deleteButton.disabled).toBe(false) + + document.removeEventListener('solid-resource-deleted', deleted) + }) + + it('does not delete when cancellation is selected', async () => { + TestResourceActionDialog.confirmed = false + const { recursiveDelete, deleteButton } = renderDeletionPane() + + deleteButton.click() + await Promise.resolve() + + expect(recursiveDelete).not.toHaveBeenCalled() + expect(deleteButton.disabled).toBe(false) + }) + + it('shows an error dialog without emitting success when deletion fails', async () => { + const { recursiveDelete, deleteButton } = renderDeletionPane() + const error = new Error('delete failed') + recursiveDelete.mockRejectedValueOnce(error) + const deleted = vi.fn() + document.addEventListener('solid-resource-deleted', deleted) + + deleteButton.click() + await flushPromises() + + const dialogs = Array.from(document.querySelectorAll('solid-panes-resource-action-dialog')) as TestResourceActionDialog[] + expect(recursiveDelete).toHaveBeenCalledOnce() + expect(deleted).not.toHaveBeenCalled() + expect(dialogs.at(-1)).toMatchObject({ + title: 'Unable to delete resource', + message: expect.stringContaining('delete failed') + }) + expect(deleteButton.disabled).toBe(false) + + document.removeEventListener('solid-resource-deleted', deleted) + }) + + it('prevents a second deletion request while the first is pending', async () => { + let resolveDelete: (() => void) | undefined + const deletion = new Promise(resolve => { + resolveDelete = resolve + }) + const { recursiveDelete, deleteButton } = renderDeletionPane() + recursiveDelete.mockReturnValueOnce(deletion) + + deleteButton.click() + await flushPromises() + expect(deleteButton.disabled).toBe(true) + + deleteButton.click() + expect(recursiveDelete).toHaveBeenCalledOnce() + + resolveDelete!() + await deletion + await flushPromises() + expect(deleteButton.disabled).toBe(false) + }) +}) From 7b7cd38736a17bed41b64d54fc2ce3add2434e38 Mon Sep 17 00:00:00 2001 From: Sharon Stratsianis Date: Sat, 7 Nov 2026 15:32:03 +1100 Subject: [PATCH 2/3] fix uri pane issue --- .../ResourceActionDialog.styles.css | 13 ++++++++++--- .../ResourceActionDialog.ts | 6 +++++- src/internal/internalPane.css | 15 +++++++++++++++ src/internal/internalPane.ts | 14 +++++++++----- test/unit/internalPane.test.ts | 16 ++++++++++++++-- 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/components/resource-action-dialog/ResourceActionDialog.styles.css b/src/components/resource-action-dialog/ResourceActionDialog.styles.css index 6c8fe64f..b1bb4f3a 100644 --- a/src/components/resource-action-dialog/ResourceActionDialog.styles.css +++ b/src/components/resource-action-dialog/ResourceActionDialog.styles.css @@ -10,6 +10,13 @@ solid-ui-dialog-header h2 { solid-ui-dialog-content p { overflow-wrap: anywhere; + white-space: pre-line; +} + +.resource-action-dialog-emphasis { + display: block; + font-weight: 600; + text-align: center; } solid-ui-button { @@ -18,7 +25,7 @@ solid-ui-button { } solid-ui-button:focus-within { - outline: 2px solid currentColor; - outline-offset: 2px; - border-radius: 5px; + outline: 1px solid currentColor; + outline-offset: 1px; + border-radius: 4px; } diff --git a/src/components/resource-action-dialog/ResourceActionDialog.ts b/src/components/resource-action-dialog/ResourceActionDialog.ts index d6625269..58d92ce0 100644 --- a/src/components/resource-action-dialog/ResourceActionDialog.ts +++ b/src/components/resource-action-dialog/ResourceActionDialog.ts @@ -105,6 +105,7 @@ export default class ResourceActionDialog extends WebComponent { } protected render () { + const [mainMessage, ...additionalMessages] = this.message.split('\n') return html` @@ -115,7 +116,10 @@ export default class ResourceActionDialog extends WebComponent { -

${this.message}

+

+ ${mainMessage} + ${additionalMessages.map(message => html`${message}`)} +

${this.confirmation diff --git a/src/internal/internalPane.css b/src/internal/internalPane.css index 36810f14..379e16ef 100644 --- a/src/internal/internalPane.css +++ b/src/internal/internalPane.css @@ -10,6 +10,21 @@ box-sizing: border-box; } +.internalPaneControls tr { + display: flex; + align-items: flex-start; + justify-content: flex-end; + gap: var(--spacing-sm, 1em); + width: 100%; +} + +.internalPaneControls td { + padding: 0; +} + .internalPaneDeleteButton { height: var(--icon-base, 2em); + display: inline-flex; + align-items: center; + justify-content: center; } diff --git a/src/internal/internalPane.ts b/src/internal/internalPane.ts index 4cb898e1..0a4dfe61 100644 --- a/src/internal/internalPane.ts +++ b/src/internal/internalPane.ts @@ -4,7 +4,7 @@ ** internal to the user's interaction with the web, and are not normally displayed */ import { solidLogicSingleton } from 'solid-logic' -import { icons, ns, widgets } from 'solid-ui' +import { icons, ns, utils, widgets } from 'solid-ui' import { BlankNode, literal, NamedNode, st, sym, Variable, Store } from 'rdflib' import { PaneDefinition } from 'pane-registry' import './internalPane.css' @@ -78,7 +78,8 @@ const pane: PaneDefinition = { 'Remove this ' + noun, () => { if (deletionPending) return - return confirmDeletion(`Are you sure you want to delete ${subject}? This cannot be undone.`) + const resourceLabel = utils.label(subject) || subject.value + return confirmDeletion(`Are you sure you want to delete "${resourceLabel}"?\nThis can't be undone.`) .then(confirmed => { if (!confirmed) return deletionPending = true @@ -86,7 +87,7 @@ const pane: PaneDefinition = { const resourceLogic = context.session.logic?.resource || solidLogicSingleton.resource return resourceLogic.recursiveDelete(subject) .then(() => { - const str = 'Deleted: ' + subject + const str = `Deleted "${resourceLabel}"` console.log(str) showMessage('Resource deleted', str) dom.dispatchEvent(new CustomEvent('solid-resource-deleted', { @@ -107,6 +108,9 @@ const pane: PaneDefinition = { ) deleteButton.classList.add('internalPaneDeleteButton') + deleteButton.style.backgroundColor = 'transparent' + deleteButton.style.border = 'none' + deleteButton.style.padding = '0' deleteCell.appendChild(deleteButton) } @@ -149,7 +153,7 @@ const pane: PaneDefinition = { st( subject, sym('http://www.w3.org/2007/ont/link#uri'), - subject.uri as any, // @@ TODO Remove casting + sym(subject.uri), store.fetcher.appNode ) ) @@ -159,7 +163,7 @@ const pane: PaneDefinition = { st( subject, sym('http://www.w3.org/2007/ont/link#documentURI'), - subject.uri.split('#')[0] as any, // @@ TODO Remove casting + sym(subject.uri.split('#')[0]), store.fetcher.appNode ) ) diff --git a/test/unit/internalPane.test.ts b/test/unit/internalPane.test.ts index 16f1d7d3..669c3362 100644 --- a/test/unit/internalPane.test.ts +++ b/test/unit/internalPane.test.ts @@ -52,8 +52,20 @@ describe('Under The Hood RDF bookkeeping', () => { internalPane.render(subject, context) internalPane.render(subject, context) - expect(appendPropertyTRs.mock.calls[0][1]).toEqual(expect.arrayContaining([ - expect.objectContaining({ predicate: sym('http://www.w3.org/2007/ont/link#uri') }) + const displayedStatements = appendPropertyTRs.mock.calls[0][1] + expect(displayedStatements).toEqual(expect.arrayContaining([ + expect.objectContaining({ + predicate: sym('http://www.w3.org/2007/ont/link#uri'), + object: subject + }) + ])) + const fragment = sym('https://example.test/file.ttl#part') + internalPane.render(fragment, context) + expect(appendPropertyTRs.mock.calls[4][1]).toEqual(expect.arrayContaining([ + expect.objectContaining({ + predicate: sym('http://www.w3.org/2007/ont/link#documentURI'), + object: sym('https://example.test/file.ttl') + }) ])) expect(store.statementsMatching(subject)).toEqual(originalStatements) expect(store.statements).toEqual(originalStatements) From 4a11b5484eb6dc41bee69d879d0cb2e750aade7d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 7 Nov 2026 12:01:20 +0000 Subject: [PATCH 3/3] chore: update solidos dependencies (dev: solid-logic@6.0.1-6 solid-ui@5.0.2-8 pane-registry@5.0.2-3 folder-pane@4.0.1-2 profile-pane@4.1.0-0 source-pane@4.0.1-0) (latest: rdflib@2.4.1 activitystreams-pane@2.0.0 chat-pane@4.0.0 contacts-pane@4.0.0 issue-pane@4.0.0 meeting-pane@4.0.0) --- package-lock.json | 114 +++++++++++++++++++++++----------------------- package.json | 8 ++-- 2 files changed, 60 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index 989ee690..01ce030c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,19 +15,19 @@ "chat-pane": "4.0.0", "contacts-pane": "4.0.0", "dompurify": "^3.4.4", - "folder-pane": "4.0.1-1", + "folder-pane": "4.0.1-2", "issue-pane": "4.0.0", "lit-html": "^3.3.2", "marked": "^18.0.3", "meeting-pane": "4.0.0", "mime-types": "^3.0.2", "package-lock.json": "^1.0.0", - "pane-registry": "5.0.2-2", + "pane-registry": "5.0.2-3", "profile-pane": "4.1.0-0", "rdflib": "2.4.1", - "solid-logic": "6.0.1-5", + "solid-logic": "6.0.1-6", "solid-namespace": "^0.5.4", - "solid-ui": "5.0.2-7", + "solid-ui": "5.0.2-8", "source-pane": "4.0.1-0" }, "devDependencies": { @@ -4146,23 +4146,23 @@ } }, "node_modules/@volar/language-core": { - "version": "2.4.30", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.30.tgz", - "integrity": "sha512-73fjwEV8wat6aOZ6U/y/hqtE30ocrIWECg67b0txGczOMNw+MffLmvJ5fnFfN9drVUEYZzLdaNo6VAy6G6avMw==", + "version": "2.4.31", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.31.tgz", + "integrity": "sha512-OM+OZfynHa1/4WlC8klqvwTQVmhYUZuwpAKdfl/rlvK8jaIozMD5qwXsOkxcRJA2tC+TEFsG1K81lNP4s/IAzQ==", "dev": true, "license": "MIT", "dependencies": { - "@volar/source-map": "2.4.30" + "@volar/source-map": "2.4.31" } }, "node_modules/@volar/language-service": { - "version": "2.4.30", - "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.30.tgz", - "integrity": "sha512-w/xDsASiqC71ggCOm7sTDXDNk/l72XEptodeRC68tgZ0e5QM4hM3zAqBGC7mBctvhrpYwKfMG+t9xcUo2hEFtA==", + "version": "2.4.31", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.31.tgz", + "integrity": "sha512-LM9HFsBVhHZ7RbFUZK9P23FvamK8XCDcN0kNgOTts02wVgnygo3ReLbmBmUZ3GuYnrYn6HHoQvqk/eHIzzTnrg==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.30", + "@volar/language-core": "2.4.31", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" @@ -4177,21 +4177,21 @@ } }, "node_modules/@volar/source-map": { - "version": "2.4.30", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.30.tgz", - "integrity": "sha512-zym1ob/KwDDyN+0jGVtxwbxJSRRnUvv5HspCtz3yEfVVdCG4/dzxJktMGPAgemqRuVs81O0o/y0q3qOUErR8kg==", + "version": "2.4.31", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.31.tgz", + "integrity": "sha512-odi31S3mKm57aO7Pd5P624FmGIB49U4lUPnYRUKvCDa8WZy5N5Yb+GMp2WJNaeIr4+q0ijNpuHsJLrfEiWvHtA==", "dev": true, "license": "MIT" }, "node_modules/@volar/typescript": { - "version": "2.4.30", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.30.tgz", - "integrity": "sha512-/DgA5tgYFPfrLFuSjIMXMGHk1LQv2+P8eny6+BhGNzFJELWIW7UZlc56ZLIRFhLsPpMfIFxM8/HvOPBggrjTZg==", + "version": "2.4.31", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.31.tgz", + "integrity": "sha512-OBHLLAi03AmaRWqzytdFhgtW2B3HM+2/vXFkvnZAlEKltalUTyu+No7g9aZx7e9efrDLOtHsd/3PgCHSfgVJ/Q==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.30", - "@volar/language-service": "2.4.30", + "@volar/language-core": "2.4.31", + "@volar/language-service": "2.4.31", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" }, @@ -6269,34 +6269,33 @@ "license": "ISC" }, "node_modules/folder-pane": { - "version": "4.0.1-1", - "resolved": "https://registry.npmjs.org/folder-pane/-/folder-pane-4.0.1-1.tgz", - "integrity": "sha512-cQSKnPUYPhDGHpK7ayUFYCeDpHoJ3XgMUPjig2Xx9YYYLye5G0RVzMGMdNi8utRCFkR46mHyMu/XNNazuOBOcw==", + "version": "4.0.1-2", + "resolved": "https://registry.npmjs.org/folder-pane/-/folder-pane-4.0.1-2.tgz", + "integrity": "sha512-R10BlGqjOYbxQuWqHthAfdGq4UcfpFfmQPzFs7mk/BIoYw9QA1WTD18ZYC5qcBhffM7KalD5gh9XwXDTJkcIGg==", "license": "MIT", "dependencies": { + "@lit/context": "^1.1.6", "lit": "^3.3.3", + "pane-registry": "5.0.2-2", "rdflib": "2.4.1", - "solid-logic": "6.0.1-4", - "solid-ui": "5.0.2-3", - "vite": "^8.0.16", - "vitest": "^4.0.18", - "vitest-axe": "^0.1.0" + "solid-logic": "6.0.1-5", + "solid-ui": "5.0.2-7" } }, "node_modules/folder-pane/node_modules/pane-registry": { - "version": "5.0.2-1", - "resolved": "https://registry.npmjs.org/pane-registry/-/pane-registry-5.0.2-1.tgz", - "integrity": "sha512-gDub2yjXejZ0JQSsEvDpUhff4e205+IIKV2iYJCbBBs65QiN/2a4wmpv2/NZJjoF6FADW+ndv8nSc6w3CxuYFg==", + "version": "5.0.2-2", + "resolved": "https://registry.npmjs.org/pane-registry/-/pane-registry-5.0.2-2.tgz", + "integrity": "sha512-W/phyBcdAHJkHnoRTl/6ddnfAUSU4aealRw2paj6tGjMLPSYxEiB6GaNcTCWOSHsBJIl4XuyHWL2vgHHAPOHTg==", "license": "MIT", "dependencies": { "rdflib": "2.4.1", - "solid-logic": "6.0.1-4" + "solid-logic": "6.0.1-5" } }, "node_modules/folder-pane/node_modules/solid-logic": { - "version": "6.0.1-4", - "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-6.0.1-4.tgz", - "integrity": "sha512-EVvS+5g91aUoONqGtM1vPnMimtwH6c/72td5b4qBRcudDjeRU/hycPg8p5abmx1YWZseLA1EFK76+E/t5bcPFw==", + "version": "6.0.1-5", + "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-6.0.1-5.tgz", + "integrity": "sha512-U6IBElhpCpNkNrJq4TujBT7VMfl8xyY0U2PiuyltI5SiORhMAtLMHOqO6liSH5PO50y7UjAOMP1G8VbLjnYIOw==", "license": "MIT", "dependencies": { "@dokieli/web-access-control": "^1.1.0", @@ -6310,9 +6309,9 @@ } }, "node_modules/folder-pane/node_modules/solid-ui": { - "version": "5.0.2-3", - "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-5.0.2-3.tgz", - "integrity": "sha512-ERESxhk6dYjoh/PcsKAwQ5UuxJbO2CTfIG2dccQrEn6/vSNpvVzsVkXsU2QCKemrSirzKiBbbB/eZTYdxLczkA==", + "version": "5.0.2-7", + "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-5.0.2-7.tgz", + "integrity": "sha512-Qt5zWWq0cy1KaZkLJEWrVnVEdOuVErfvfuUEHKFTFi41FzmMtTD/5k2uMgnT3Gb5zzT2z5przxJWyqpWfmU+TA==", "license": "MIT", "dependencies": { "@awesome.me/webawesome": "^3.9.0", @@ -6335,9 +6334,9 @@ "escape-html": "^1.0.3", "lit": "^3.3.3", "mime-types": "^3.0.2", - "pane-registry": "5.0.2-1", + "pane-registry": "5.0.2-2", "rdflib": "2.4.1", - "solid-logic": "6.0.1-4", + "solid-logic": "6.0.1-5", "solid-namespace": "^0.5.4", "uuid": "^14.0.0" }, @@ -8997,13 +8996,13 @@ "license": "MIT" }, "node_modules/pane-registry": { - "version": "5.0.2-2", - "resolved": "https://registry.npmjs.org/pane-registry/-/pane-registry-5.0.2-2.tgz", - "integrity": "sha512-W/phyBcdAHJkHnoRTl/6ddnfAUSU4aealRw2paj6tGjMLPSYxEiB6GaNcTCWOSHsBJIl4XuyHWL2vgHHAPOHTg==", + "version": "5.0.2-3", + "resolved": "https://registry.npmjs.org/pane-registry/-/pane-registry-5.0.2-3.tgz", + "integrity": "sha512-yYTteGX/ld14YeLnpTN77ygseUzlH6IC0k2A6Du4vqEXg97Tk0MHeqqVyiVCz3K2e0eBFyqUVQ8cDheLz/9JVg==", "license": "MIT", "dependencies": { "rdflib": "2.4.1", - "solid-logic": "6.0.1-5" + "solid-logic": "6.0.1-6" } }, "node_modules/parent-module": { @@ -10078,16 +10077,15 @@ } }, "node_modules/solid-logic": { - "version": "6.0.1-5", - "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-6.0.1-5.tgz", - "integrity": "sha512-U6IBElhpCpNkNrJq4TujBT7VMfl8xyY0U2PiuyltI5SiORhMAtLMHOqO6liSH5PO50y7UjAOMP1G8VbLjnYIOw==", + "version": "6.0.1-6", + "resolved": "https://registry.npmjs.org/solid-logic/-/solid-logic-6.0.1-6.tgz", + "integrity": "sha512-qlvgCfutwGbWZZExkNzmXzSDj8FhAQ//IeCe5zWluyLCfySH+CHihbYX48FNyY6EqEC8M/v9UE0JVSSEuJsSdg==", "license": "MIT", "dependencies": { "@dokieli/web-access-control": "^1.1.0", "@uvdsl/solid-oidc-client-browser": "^0.2.3", - "solid-namespace": "^0.5.4", - "vite": "^8.0.16", - "vitest": "^4.1.9" + "mime-types": "^3.0.2", + "solid-namespace": "^0.5.4" }, "engines": { "node": ">=18" @@ -10100,9 +10098,9 @@ "license": "MIT" }, "node_modules/solid-ui": { - "version": "5.0.2-7", - "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-5.0.2-7.tgz", - "integrity": "sha512-Qt5zWWq0cy1KaZkLJEWrVnVEdOuVErfvfuUEHKFTFi41FzmMtTD/5k2uMgnT3Gb5zzT2z5przxJWyqpWfmU+TA==", + "version": "5.0.2-8", + "resolved": "https://registry.npmjs.org/solid-ui/-/solid-ui-5.0.2-8.tgz", + "integrity": "sha512-xMWmA4gLYSZ+drhk5DrnKdPn4QlDX8vB534YsaaktPJrJaT6/KhhLynO3peS341F+9I5P+Q2C2CiOGbfSgQqoA==", "license": "MIT", "dependencies": { "@awesome.me/webawesome": "^3.9.0", @@ -10125,9 +10123,9 @@ "escape-html": "^1.0.3", "lit": "^3.3.3", "mime-types": "^3.0.2", - "pane-registry": "5.0.2-2", + "pane-registry": "5.0.2-3", "rdflib": "2.4.1", - "solid-logic": "6.0.1-5", + "solid-logic": "6.0.1-6", "solid-namespace": "^0.5.4", "uuid": "^14.0.0" }, @@ -10630,9 +10628,9 @@ } }, "node_modules/tough-cookie": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", - "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.1.0.tgz", + "integrity": "sha512-FTxc8u8Kl2tz0Lat1u8ksvcy8bwHRyEMawwTWybo51JqaC6O7SvfuhAKg+Ld6VqyIe2QRJBzY9HA7y2WyFRT2w==", "devOptional": true, "license": "BSD-3-Clause", "dependencies": { diff --git a/package.json b/package.json index 5aad5f94..d241390a 100644 --- a/package.json +++ b/package.json @@ -62,19 +62,19 @@ "chat-pane": "4.0.0", "contacts-pane": "4.0.0", "dompurify": "^3.4.4", - "folder-pane": "4.0.1-1", + "folder-pane": "4.0.1-2", "issue-pane": "4.0.0", "lit-html": "^3.3.2", "marked": "^18.0.3", "meeting-pane": "4.0.0", "mime-types": "^3.0.2", "package-lock.json": "^1.0.0", - "pane-registry": "5.0.2-2", + "pane-registry": "5.0.2-3", "profile-pane": "4.1.0-0", "rdflib": "2.4.1", - "solid-logic": "6.0.1-5", + "solid-logic": "6.0.1-6", "solid-namespace": "^0.5.4", - "solid-ui": "5.0.2-7", + "solid-ui": "5.0.2-8", "source-pane": "4.0.1-0" }, "overrides": {