From 93955faf761cb2d2286934a9166fcca68c961453 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjean Date: Thu, 20 Feb 2025 07:37:47 -0800 Subject: [PATCH] CRISTAL-326: Missing tooltip and hints for the create page UI (#684) * Introduce NavigationTreeSelect as a new form component * Add help text to TextField component * Improve form styling for Vuetify * Update hierarchy API to use EntityReferences instead of PageData * Update navigation tree API to use EntityReferences instead of PageData * Update document change API to use EntityReferences instead of PageData * Improve buttons behavior on form modals * Overall style uniformization for existing forms * Add Select button to tree select --- core/document/document-api/src/index.ts | 13 +- .../src/defaultDocumentService.ts | 6 +- core/hierarchy/hierarchy-api/package.json | 3 +- core/hierarchy/hierarchy-api/src/index.ts | 12 +- core/hierarchy/hierarchy-default/package.json | 1 + .../src/components/componentsInit.ts | 13 +- core/hierarchy/hierarchy-default/src/utils.ts | 29 +++-- .../hierarchy-filesystem/package.json | 1 + .../src/components/componentsInit.ts | 12 +- core/hierarchy/hierarchy-github/package.json | 1 + .../src/components/componentsInit.ts | 19 ++- .../hierarchy-nextcloud/package.json | 1 + .../src/components/componentsInit.ts | 12 +- .../src/components/componentsInit.ts | 23 ++-- .../navigation-tree-api/src/index.ts | 11 +- .../navigation-tree-default/package.json | 1 + .../src/components/componentsInit.ts | 5 +- .../navigation-tree-default/src/utils.ts | 21 +-- .../src/components/componentsInit.ts | 5 +- .../src/components/componentsInit.ts | 5 +- .../src/components/componentsInit.ts | 5 +- .../src/components/componentsInit.ts | 30 ++--- .../page-actions-ui/langs/translation-en.json | 3 + .../page-actions-ui/langs/translation-fr.json | 3 + .../page-actions-ui/src/vue/DeletePage.vue | 7 +- .../page-actions-ui/src/vue/MovePage.vue | 73 +++++------ .../page-actions-ui/src/vue/PageActions.vue | 3 + .../src/vue/PageActionsCategory.vue | 3 + .../page-actions-ui/src/vue/RenamePage.vue | 36 +++--- ds/api/package.json | 1 + ds/api/src/XNavigationTree.ts | 8 +- ds/api/src/XNavigationTreeSelect.ts | 36 ++++++ ds/api/src/XTextField.ts | 5 + ds/api/src/index.ts | 5 + ds/shoelace/langs/translation-en.json | 4 + ds/shoelace/langs/translation-fr.json | 4 + ds/shoelace/package.json | 6 +- .../components/shoelaceDesignSystemLoader.ts | 5 + ds/shoelace/src/translations.ts | 28 ++++ ds/shoelace/src/vue/form/x-form.vue | 8 +- .../src/vue/form/x-navigation-tree-select.vue | 115 +++++++++++++++++ ds/shoelace/src/vue/form/x-text-field.vue | 1 + ds/shoelace/src/vue/x-navigation-tree.vue | 23 ++-- ds/vuetify/langs/translation-en.json | 4 +- ds/vuetify/langs/translation-fr.json | 4 +- ds/vuetify/package.json | 2 + .../components/vuetifyDesignSystemLoader.ts | 5 + ds/vuetify/src/translations.ts | 4 + ds/vuetify/src/vue/form/x-form.vue | 12 +- .../src/vue/form/x-navigation-tree-select.vue | 120 ++++++++++++++++++ ds/vuetify/src/vue/form/x-text-field.vue | 2 + ds/vuetify/src/vue/x-navigation-tree.vue | 32 ++--- editors/tiptap/src/vue/c-edit-tiptap.vue | 8 +- pnpm-lock.yaml | 39 ++++++ skin/langs/translation-en.json | 3 + skin/langs/translation-fr.json | 3 + skin/src/vue/__tests__/c-content.test.ts | 9 ++ skin/src/vue/c-article.vue | 6 +- skin/src/vue/c-content.vue | 3 + skin/src/vue/c-page-creation-menu.vue | 71 ++++------- skin/src/vue/c-sidebar.vue | 13 +- web/e2e/main-page.spec.ts | 8 +- 62 files changed, 698 insertions(+), 256 deletions(-) create mode 100644 ds/api/src/XNavigationTreeSelect.ts create mode 100644 ds/shoelace/langs/translation-en.json create mode 100644 ds/shoelace/langs/translation-fr.json create mode 100644 ds/shoelace/src/translations.ts create mode 100644 ds/shoelace/src/vue/form/x-navigation-tree-select.vue create mode 100644 ds/vuetify/src/vue/form/x-navigation-tree-select.vue diff --git a/core/document/document-api/src/index.ts b/core/document/document-api/src/index.ts index bff39e25e..2c4a8a2ca 100644 --- a/core/document/document-api/src/index.ts +++ b/core/document/document-api/src/index.ts @@ -101,21 +101,24 @@ interface DocumentService { * made on the whole Cristal instance. * @param change - the kind of change * @param listener - the listener to register - * @since 0.12 + * @since 0.15 */ registerDocumentChangeListener( change: DocumentChange, - listener: (page: PageData) => Promise, + listener: (page: DocumentReference) => Promise, ): void; /** * Notify that a document change happened. This will execute all registered * listeners for the given kind of change. * @param change - the kind of change - * @param page - the document changed - * @since 0.12 + * @param page - the reference to the changed document + * @since 0.15 */ - notifyDocumentChange(change: DocumentChange, page: PageData): Promise; + notifyDocumentChange( + change: DocumentChange, + page: DocumentReference, + ): Promise; } const name: string = "DocumentService"; diff --git a/core/document/document-default/src/defaultDocumentService.ts b/core/document/document-default/src/defaultDocumentService.ts index 74875677b..501cae7ce 100644 --- a/core/document/document-default/src/defaultDocumentService.ts +++ b/core/document/document-default/src/defaultDocumentService.ts @@ -187,7 +187,7 @@ export class DefaultDocumentService implements DocumentService { private readonly store: DocumentStore; private readonly documentChangeListeners: Map< DocumentChange, - Array<(page: PageData) => Promise> + Array<(page: DocumentReference) => Promise> > = new Map(); constructor(@inject("CristalApp") private cristal: CristalApp) { @@ -242,7 +242,7 @@ export class DefaultDocumentService implements DocumentService { registerDocumentChangeListener( change: DocumentChange, - listener: (page: PageData) => Promise, + listener: (page: DocumentReference) => Promise, ): void { if (this.documentChangeListeners.has(change)) { this.documentChangeListeners.get(change)!.push(listener); @@ -253,7 +253,7 @@ export class DefaultDocumentService implements DocumentService { async notifyDocumentChange( change: DocumentChange, - page: PageData, + page: DocumentReference, ): Promise { if (this.documentChangeListeners.has(change)) { await Promise.all( diff --git a/core/hierarchy/hierarchy-api/package.json b/core/hierarchy/hierarchy-api/package.json index 66d9ae7c4..1861f4033 100644 --- a/core/hierarchy/hierarchy-api/package.json +++ b/core/hierarchy/hierarchy-api/package.json @@ -23,7 +23,8 @@ "test": "vitest --run" }, "dependencies": { - "@xwiki/cristal-api": "workspace:*" + "@xwiki/cristal-api": "workspace:*", + "@xwiki/cristal-model-api": "workspace:*" }, "devDependencies": { "@xwiki/cristal-dev-config": "workspace:*", diff --git a/core/hierarchy/hierarchy-api/src/index.ts b/core/hierarchy/hierarchy-api/src/index.ts index 181862a22..a90678704 100644 --- a/core/hierarchy/hierarchy-api/src/index.ts +++ b/core/hierarchy/hierarchy-api/src/index.ts @@ -18,7 +18,10 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ -import type { PageData } from "@xwiki/cristal-api"; +import type { + DocumentReference, + SpaceReference, +} from "@xwiki/cristal-model-api"; /** * Description of a hierarchy item for a given page. @@ -39,10 +42,13 @@ interface PageHierarchyResolver { /** * Returns the page hierarchy for a given page. * - * @param pageData - the page for which to compute the hierarchy + * @param page - the reference to the page for which to compute the hierarchy * @returns the page hierarchy + * @since 0.15 */ - getPageHierarchy(pageData: PageData): Promise>; + getPageHierarchy( + page: DocumentReference | SpaceReference, + ): Promise>; } /** diff --git a/core/hierarchy/hierarchy-default/package.json b/core/hierarchy/hierarchy-default/package.json index efedb4c03..c36b49233 100644 --- a/core/hierarchy/hierarchy-default/package.json +++ b/core/hierarchy/hierarchy-default/package.json @@ -25,6 +25,7 @@ "dependencies": { "@xwiki/cristal-api": "workspace:*", "@xwiki/cristal-hierarchy-api": "workspace:*", + "@xwiki/cristal-model-api": "workspace:*", "inversify": "6.2.2" }, "peerDependencies": { diff --git a/core/hierarchy/hierarchy-default/src/components/componentsInit.ts b/core/hierarchy/hierarchy-default/src/components/componentsInit.ts index 60e7b5243..8e0d2cf0e 100644 --- a/core/hierarchy/hierarchy-default/src/components/componentsInit.ts +++ b/core/hierarchy/hierarchy-default/src/components/componentsInit.ts @@ -19,13 +19,18 @@ */ import { name as PageHierarchyResolverName } from "@xwiki/cristal-hierarchy-api"; +import { EntityType } from "@xwiki/cristal-model-api"; import { Container, inject, injectable } from "inversify"; -import type { CristalApp, Logger, PageData } from "@xwiki/cristal-api"; +import type { CristalApp, Logger } from "@xwiki/cristal-api"; import type { PageHierarchyItem, PageHierarchyResolver, PageHierarchyResolverProvider, } from "@xwiki/cristal-hierarchy-api"; +import type { + DocumentReference, + SpaceReference, +} from "@xwiki/cristal-model-api"; /** * Default implementation for PageHierarchyResolver. @@ -48,7 +53,7 @@ class DefaultPageHierarchyResolver implements PageHierarchyResolver { } async getPageHierarchy( - pageData: PageData, + page: DocumentReference | SpaceReference, ): Promise> { const hierarchy: Array = [ { @@ -60,9 +65,9 @@ class DefaultPageHierarchyResolver implements PageHierarchyResolver { }).href, }, ]; - if (pageData != null) { + if (page.type == EntityType.DOCUMENT) { hierarchy.push({ - label: pageData.name, + label: (page as DocumentReference).name, pageId: this.cristalApp.getCurrentPage(), url: window.location.href, }); diff --git a/core/hierarchy/hierarchy-default/src/utils.ts b/core/hierarchy/hierarchy-default/src/utils.ts index 729efdc45..ac9a1f73b 100644 --- a/core/hierarchy/hierarchy-default/src/utils.ts +++ b/core/hierarchy/hierarchy-default/src/utils.ts @@ -18,37 +18,46 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ +import { EntityType } from "@xwiki/cristal-model-api"; import type { CristalApp, PageData } from "@xwiki/cristal-api"; import type { PageHierarchyItem } from "@xwiki/cristal-hierarchy-api"; +import type { + DocumentReference, + SpaceReference, +} from "@xwiki/cristal-model-api"; /** * Returns the page hierarchy for a path-like page id. * This does not include a Home segment. * - * @param pageData - the data of the page + * @param page - the reference to the page * @param cristalApp - the current app * @returns the page hierarchy - * @since 0.10 + * @since 0.15 **/ // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule. // eslint-disable-next-line max-statements export async function getPageHierarchyFromPath( - pageData: PageData, + page: DocumentReference | SpaceReference, cristalApp: CristalApp, ): Promise> { const hierarchy: Array = []; - const fileHierarchy = pageData.id.split("/"); + const fileHierarchy = []; + if (page.type == EntityType.SPACE) { + fileHierarchy.push(...(page as SpaceReference).names); + } else if (page.type == EntityType.DOCUMENT) { + fileHierarchy.push( + ...(page as DocumentReference).space!.names, + (page as DocumentReference).name, + ); + } let currentFile = ""; for (let i = 0; i < fileHierarchy.length; i++) { const file = fileHierarchy[i]; currentFile += `${i == 0 ? "" : "/"}${file}`; - let currentPageData: PageData | undefined; - if (i == fileHierarchy.length - 1) { - currentPageData = pageData; - } else { - currentPageData = await cristalApp.getPage(currentFile); - } + const currentPageData: PageData | undefined = + await cristalApp.getPage(currentFile); hierarchy.push({ label: currentPageData?.name ? currentPageData.name : file, pageId: currentFile, diff --git a/core/hierarchy/hierarchy-filesystem/package.json b/core/hierarchy/hierarchy-filesystem/package.json index 81cf0ae10..67e237ef6 100644 --- a/core/hierarchy/hierarchy-filesystem/package.json +++ b/core/hierarchy/hierarchy-filesystem/package.json @@ -26,6 +26,7 @@ "@xwiki/cristal-api": "workspace:*", "@xwiki/cristal-hierarchy-api": "workspace:*", "@xwiki/cristal-hierarchy-default": "workspace:*", + "@xwiki/cristal-model-api": "workspace:*", "inversify": "6.2.2" }, "peerDependencies": { diff --git a/core/hierarchy/hierarchy-filesystem/src/components/componentsInit.ts b/core/hierarchy/hierarchy-filesystem/src/components/componentsInit.ts index d71659edf..7ca936a12 100644 --- a/core/hierarchy/hierarchy-filesystem/src/components/componentsInit.ts +++ b/core/hierarchy/hierarchy-filesystem/src/components/componentsInit.ts @@ -21,11 +21,15 @@ import { name } from "@xwiki/cristal-hierarchy-api"; import { getPageHierarchyFromPath } from "@xwiki/cristal-hierarchy-default"; import { Container, inject, injectable } from "inversify"; -import type { CristalApp, Logger, PageData } from "@xwiki/cristal-api"; +import type { CristalApp, Logger } from "@xwiki/cristal-api"; import type { PageHierarchyItem, PageHierarchyResolver, } from "@xwiki/cristal-hierarchy-api"; +import type { + DocumentReference, + SpaceReference, +} from "@xwiki/cristal-model-api"; /** * Implementation of PageHierarchyResolver for the FileSystem backend. @@ -49,7 +53,7 @@ class FileSystemPageHierarchyResolver implements PageHierarchyResolver { } async getPageHierarchy( - pageData: PageData, + page: DocumentReference | SpaceReference, ): Promise> { let hierarchy: Array = [ { @@ -61,9 +65,9 @@ class FileSystemPageHierarchyResolver implements PageHierarchyResolver { }).href, }, ]; - if (pageData != null) { + if (page != null) { hierarchy = hierarchy.concat( - await getPageHierarchyFromPath(pageData, this.cristalApp), + await getPageHierarchyFromPath(page, this.cristalApp), ); } return hierarchy; diff --git a/core/hierarchy/hierarchy-github/package.json b/core/hierarchy/hierarchy-github/package.json index 581e9b581..06ee4d2b4 100644 --- a/core/hierarchy/hierarchy-github/package.json +++ b/core/hierarchy/hierarchy-github/package.json @@ -25,6 +25,7 @@ "dependencies": { "@xwiki/cristal-api": "workspace:*", "@xwiki/cristal-hierarchy-api": "workspace:*", + "@xwiki/cristal-model-api": "workspace:*", "inversify": "6.2.2" }, "peerDependencies": { diff --git a/core/hierarchy/hierarchy-github/src/components/componentsInit.ts b/core/hierarchy/hierarchy-github/src/components/componentsInit.ts index cc281da97..50c3d2686 100644 --- a/core/hierarchy/hierarchy-github/src/components/componentsInit.ts +++ b/core/hierarchy/hierarchy-github/src/components/componentsInit.ts @@ -19,12 +19,17 @@ */ import { name } from "@xwiki/cristal-hierarchy-api"; +import { EntityType } from "@xwiki/cristal-model-api"; import { Container, inject, injectable } from "inversify"; -import type { CristalApp, Logger, PageData } from "@xwiki/cristal-api"; +import type { CristalApp, Logger } from "@xwiki/cristal-api"; import type { PageHierarchyItem, PageHierarchyResolver, } from "@xwiki/cristal-hierarchy-api"; +import type { + DocumentReference, + SpaceReference, +} from "@xwiki/cristal-model-api"; /** * Implementation of PageHierarchyResolver for the GitHub backend. @@ -46,7 +51,7 @@ class GitHubPageHierarchyResolver implements PageHierarchyResolver { } async getPageHierarchy( - pageData: PageData, + page: DocumentReference | SpaceReference, ): Promise> { const hierarchy: Array = [ { @@ -58,8 +63,14 @@ class GitHubPageHierarchyResolver implements PageHierarchyResolver { }).href, }, ]; - if (pageData != null) { - const fileHierarchy = pageData.id.split("/"); + if (page != null) { + const fileHierarchy = + page.type == EntityType.SPACE + ? [...(page as SpaceReference).names] + : [ + ...(page as DocumentReference).space!.names, + (page as DocumentReference).name, + ]; let currentFile = ""; for (let i = 0; i < fileHierarchy.length; i++) { const file = fileHierarchy[i]; diff --git a/core/hierarchy/hierarchy-nextcloud/package.json b/core/hierarchy/hierarchy-nextcloud/package.json index a3a4c5593..c6bc01f13 100644 --- a/core/hierarchy/hierarchy-nextcloud/package.json +++ b/core/hierarchy/hierarchy-nextcloud/package.json @@ -26,6 +26,7 @@ "@xwiki/cristal-api": "workspace:*", "@xwiki/cristal-hierarchy-api": "workspace:*", "@xwiki/cristal-hierarchy-default": "workspace:*", + "@xwiki/cristal-model-api": "workspace:*", "inversify": "6.2.2" }, "peerDependencies": { diff --git a/core/hierarchy/hierarchy-nextcloud/src/components/componentsInit.ts b/core/hierarchy/hierarchy-nextcloud/src/components/componentsInit.ts index 536e9c70f..ab4bc88fc 100644 --- a/core/hierarchy/hierarchy-nextcloud/src/components/componentsInit.ts +++ b/core/hierarchy/hierarchy-nextcloud/src/components/componentsInit.ts @@ -21,11 +21,15 @@ import { name } from "@xwiki/cristal-hierarchy-api"; import { getPageHierarchyFromPath } from "@xwiki/cristal-hierarchy-default"; import { Container, inject, injectable } from "inversify"; -import type { CristalApp, Logger, PageData } from "@xwiki/cristal-api"; +import type { CristalApp, Logger } from "@xwiki/cristal-api"; import type { PageHierarchyItem, PageHierarchyResolver, } from "@xwiki/cristal-hierarchy-api"; +import type { + DocumentReference, + SpaceReference, +} from "@xwiki/cristal-model-api"; /** * Implementation of PageHierarchyResolver for Nextcloud backend. @@ -47,7 +51,7 @@ class NextcloudPageHierarchyResolver implements PageHierarchyResolver { } async getPageHierarchy( - pageData: PageData, + page: DocumentReference | SpaceReference, ): Promise> { let hierarchy: Array = [ { @@ -59,9 +63,9 @@ class NextcloudPageHierarchyResolver implements PageHierarchyResolver { }).href, }, ]; - if (pageData != null) { + if (page != null) { hierarchy = hierarchy.concat( - await getPageHierarchyFromPath(pageData, this.cristalApp), + await getPageHierarchyFromPath(page, this.cristalApp), ); } return hierarchy; diff --git a/core/hierarchy/hierarchy-xwiki/src/components/componentsInit.ts b/core/hierarchy/hierarchy-xwiki/src/components/componentsInit.ts index f954ece26..dc53c7621 100644 --- a/core/hierarchy/hierarchy-xwiki/src/components/componentsInit.ts +++ b/core/hierarchy/hierarchy-xwiki/src/components/componentsInit.ts @@ -19,16 +19,20 @@ */ import { name } from "@xwiki/cristal-hierarchy-api"; +import { EntityType } from "@xwiki/cristal-model-api"; import { getRestSpacesApiUrl } from "@xwiki/cristal-xwiki-utils"; import { Container, inject, injectable, named } from "inversify"; -import type { CristalApp, Logger, PageData } from "@xwiki/cristal-api"; +import type { CristalApp, Logger } from "@xwiki/cristal-api"; import type { AuthenticationManagerProvider } from "@xwiki/cristal-authentication-api"; import type { StorageProvider } from "@xwiki/cristal-backend-api"; import type { PageHierarchyItem, PageHierarchyResolver, } from "@xwiki/cristal-hierarchy-api"; -import type { DocumentReference } from "@xwiki/cristal-model-api"; +import type { + DocumentReference, + SpaceReference, +} from "@xwiki/cristal-model-api"; import type { ModelReferenceSerializer } from "@xwiki/cristal-model-reference-api"; import type { RemoteURLParser } from "@xwiki/cristal-model-remote-url-api"; @@ -61,14 +65,11 @@ class XWikiPageHierarchyResolver implements PageHierarchyResolver { // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule. // eslint-disable-next-line max-statements async getPageHierarchy( - pageData: PageData, + page: DocumentReference | SpaceReference, ): Promise> { - const documentId = pageData.document.getIdentifier(); - if (documentId == null) { - this.logger.debug( - `No identifier found for page ${pageData.name}, falling back to default hierarchy resolver.`, - ); - return this.defaultHierarchyResolver.getPageHierarchy(pageData); + let documentId = this.referenceSerializer.serialize(page)!; + if (page.type == EntityType.SPACE) { + documentId += ".WebHome"; } // TODO: support subwikis. const restApiUrl = getRestSpacesApiUrl( @@ -121,9 +122,9 @@ class XWikiPageHierarchyResolver implements PageHierarchyResolver { } catch (error) { this.logger.error(error); this.logger.debug( - `Could not load hierarchy for page ${pageData.name}, falling back to default hierarchy resolver.`, + `Could not load hierarchy for page ${documentId}, falling back to default hierarchy resolver.`, ); - return this.defaultHierarchyResolver.getPageHierarchy(pageData); + return this.defaultHierarchyResolver.getPageHierarchy(page); } } } diff --git a/core/navigation-tree/navigation-tree-api/src/index.ts b/core/navigation-tree/navigation-tree-api/src/index.ts index bd1493687..769b4e11e 100644 --- a/core/navigation-tree/navigation-tree-api/src/index.ts +++ b/core/navigation-tree/navigation-tree-api/src/index.ts @@ -18,8 +18,10 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ -import type { PageData } from "@xwiki/cristal-api"; -import type { SpaceReference } from "@xwiki/cristal-model-api"; +import type { + DocumentReference, + SpaceReference, +} from "@xwiki/cristal-model-api"; /** * Description of a navigation tree node. @@ -53,10 +55,11 @@ interface NavigationTreeSource { /** * Returns the ids of the parents nodes for a given page. * - * @param page - the data of the page + * @param page - the reference to the page * @returns the parents nodes ids + * @since 0.15 **/ - getParentNodesId(page?: PageData): Array; + getParentNodesId(page?: DocumentReference): Array; } /** diff --git a/core/navigation-tree/navigation-tree-default/package.json b/core/navigation-tree/navigation-tree-default/package.json index 175a55ce6..82683e720 100644 --- a/core/navigation-tree/navigation-tree-default/package.json +++ b/core/navigation-tree/navigation-tree-default/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "@xwiki/cristal-api": "workspace:*", + "@xwiki/cristal-model-api": "workspace:*", "@xwiki/cristal-navigation-tree-api": "workspace:*", "inversify": "6.2.2" }, diff --git a/core/navigation-tree/navigation-tree-default/src/components/componentsInit.ts b/core/navigation-tree/navigation-tree-default/src/components/componentsInit.ts index e47730e24..c1e9e9db4 100644 --- a/core/navigation-tree/navigation-tree-default/src/components/componentsInit.ts +++ b/core/navigation-tree/navigation-tree-default/src/components/componentsInit.ts @@ -21,7 +21,8 @@ import { getParentNodesIdFromPath } from "../utils"; import { name as NavigationTreeSourceName } from "@xwiki/cristal-navigation-tree-api"; import { Container, inject, injectable } from "inversify"; -import type { CristalApp, Logger, PageData } from "@xwiki/cristal-api"; +import type { CristalApp, Logger } from "@xwiki/cristal-api"; +import type { DocumentReference } from "@xwiki/cristal-model-api"; import type { NavigationTreeNode, NavigationTreeSource, @@ -48,7 +49,7 @@ class DefaultNavigationTreeSource implements NavigationTreeSource { return []; } - getParentNodesId(page?: PageData): Array { + getParentNodesId(page?: DocumentReference): Array { return getParentNodesIdFromPath(page); } } diff --git a/core/navigation-tree/navigation-tree-default/src/utils.ts b/core/navigation-tree/navigation-tree-default/src/utils.ts index a1ac31258..e1e83c8a4 100644 --- a/core/navigation-tree/navigation-tree-default/src/utils.ts +++ b/core/navigation-tree/navigation-tree-default/src/utils.ts @@ -18,30 +18,31 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ -import type { PageData } from "@xwiki/cristal-api"; +import type { DocumentReference } from "@xwiki/cristal-model-api"; /** * Returns the ids of the parents nodes for a path-like page id. * - * @param pageData - the data of the page + * @param pageData - the page * @returns the parents nodes ids - * @since 0.10 + * @since 0.15 **/ -// TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule. -// eslint-disable-next-line max-statements -export function getParentNodesIdFromPath(page?: PageData): Array { +export function getParentNodesIdFromPath( + page?: DocumentReference, +): Array { const result: Array = []; if (page) { - // TODO: Use a page resolver instead when CRISTAL-234 is fixed. - const parents = page.id.split("/"); + const parents = [ + ...(page as DocumentReference).space!.names, + (page as DocumentReference).name, + ]; let currentParent = ""; let i; - for (i = 0; i < parents.length - 1; i++) { + for (i = 0; i < parents.length; i++) { currentParent += parents[i]; result.push(currentParent); currentParent += "/"; } - result.push(page.id); } return result; } diff --git a/core/navigation-tree/navigation-tree-filesystem/src/components/componentsInit.ts b/core/navigation-tree/navigation-tree-filesystem/src/components/componentsInit.ts index 0f90f1c4a..3545ac232 100644 --- a/core/navigation-tree/navigation-tree-filesystem/src/components/componentsInit.ts +++ b/core/navigation-tree/navigation-tree-filesystem/src/components/componentsInit.ts @@ -22,7 +22,8 @@ import { SpaceReference } from "@xwiki/cristal-model-api"; import { name as NavigationTreeSourceName } from "@xwiki/cristal-navigation-tree-api"; import { getParentNodesIdFromPath } from "@xwiki/cristal-navigation-tree-default"; import { Container, inject, injectable } from "inversify"; -import type { CristalApp, Logger, PageData } from "@xwiki/cristal-api"; +import type { CristalApp, Logger } from "@xwiki/cristal-api"; +import type { DocumentReference } from "@xwiki/cristal-model-api"; import type { NavigationTreeNode, NavigationTreeSource, @@ -86,7 +87,7 @@ class FileSystemNavigationTreeSource implements NavigationTreeSource { return navigationTree; } - getParentNodesId(page?: PageData): Array { + getParentNodesId(page?: DocumentReference): Array { return getParentNodesIdFromPath(page); } } diff --git a/core/navigation-tree/navigation-tree-github/src/components/componentsInit.ts b/core/navigation-tree/navigation-tree-github/src/components/componentsInit.ts index 7285a71cb..a5e657f5b 100644 --- a/core/navigation-tree/navigation-tree-github/src/components/componentsInit.ts +++ b/core/navigation-tree/navigation-tree-github/src/components/componentsInit.ts @@ -22,7 +22,8 @@ import { SpaceReference } from "@xwiki/cristal-model-api"; import { name as NavigationTreeSourceName } from "@xwiki/cristal-navigation-tree-api"; import { getParentNodesIdFromPath } from "@xwiki/cristal-navigation-tree-default"; import { Container, inject, injectable } from "inversify"; -import type { CristalApp, Logger, PageData } from "@xwiki/cristal-api"; +import type { CristalApp, Logger } from "@xwiki/cristal-api"; +import type { DocumentReference } from "@xwiki/cristal-model-api"; import type { NavigationTreeNode, NavigationTreeSource, @@ -94,7 +95,7 @@ class GitHubNavigationTreeSource implements NavigationTreeSource { return navigationTree; } - getParentNodesId(page?: PageData): Array { + getParentNodesId(page?: DocumentReference): Array { return getParentNodesIdFromPath(page); } } diff --git a/core/navigation-tree/navigation-tree-nextcloud/src/components/componentsInit.ts b/core/navigation-tree/navigation-tree-nextcloud/src/components/componentsInit.ts index f60e0850d..8c07f714a 100644 --- a/core/navigation-tree/navigation-tree-nextcloud/src/components/componentsInit.ts +++ b/core/navigation-tree/navigation-tree-nextcloud/src/components/componentsInit.ts @@ -23,7 +23,8 @@ import { SpaceReference } from "@xwiki/cristal-model-api"; import { name as NavigationTreeSourceName } from "@xwiki/cristal-navigation-tree-api"; import { getParentNodesIdFromPath } from "@xwiki/cristal-navigation-tree-default"; import { Container, inject, injectable } from "inversify"; -import type { CristalApp, Logger, PageData } from "@xwiki/cristal-api"; +import type { CristalApp, Logger } from "@xwiki/cristal-api"; +import type { DocumentReference } from "@xwiki/cristal-model-api"; import type { NavigationTreeNode, NavigationTreeSource, @@ -123,7 +124,7 @@ class NextcloudNavigationTreeSource implements NavigationTreeSource { return subdirectories; } - getParentNodesId(page?: PageData): Array { + getParentNodesId(page?: DocumentReference): Array { return getParentNodesIdFromPath(page); } diff --git a/core/navigation-tree/navigation-tree-xwiki/src/components/componentsInit.ts b/core/navigation-tree/navigation-tree-xwiki/src/components/componentsInit.ts index 4ee062262..ae5a3ac15 100644 --- a/core/navigation-tree/navigation-tree-xwiki/src/components/componentsInit.ts +++ b/core/navigation-tree/navigation-tree-xwiki/src/components/componentsInit.ts @@ -20,7 +20,7 @@ import { name as NavigationTreeSourceName } from "@xwiki/cristal-navigation-tree-api"; import { Container, inject, injectable, named } from "inversify"; -import type { CristalApp, Logger, PageData } from "@xwiki/cristal-api"; +import type { CristalApp, Logger } from "@xwiki/cristal-api"; import type { AuthenticationManagerProvider } from "@xwiki/cristal-authentication-api"; import type { DocumentReference } from "@xwiki/cristal-model-api"; import type { ModelReferenceSerializer } from "@xwiki/cristal-model-reference-api"; @@ -80,31 +80,21 @@ class XWikiNavigationTreeSource implements NavigationTreeSource { return navigationTree; } - // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule. - // eslint-disable-next-line max-statements - getParentNodesId(page?: PageData): Array { + getParentNodesId(page?: DocumentReference): Array { const result = []; - if (page) { - const documentId = page.document.getIdentifier(); - if (!documentId) { - this.logger.debug( - `No identifier found for page ${page.name}, cannot resolve parents.`, - ); - return []; - } - // TODO: Use a page resolver instead when CRISTAL-234 is fixed. - const parents = documentId - .replace(/\.WebHome$/, "") - .split(/(?(); const cristal: CristalApp = inject("cristal")!; @@ -56,7 +58,7 @@ async function deletePage() { .getContainer() .get("PageHierarchyResolverProvider") .get() - .getPageHierarchy(props.currentPage!); + .getPageHierarchy(props.currentPageReference); const storage = cristal .getContainer() @@ -66,7 +68,7 @@ async function deletePage() { deleteDialogOpen.value = false; if (result.success) { - const deletedPage = props.currentPage!; + documentService.notifyDocumentChange("delete", props.currentPageReference); if (hierarchy.length > 1) { cristal.setCurrentPage(hierarchy[hierarchy.length - 2].pageId, "view"); } else { @@ -77,7 +79,6 @@ async function deletePage() { page: props.currentPageName, }), ); - documentService.notifyDocumentChange("delete", deletedPage); } else { alertsService.error( t("page.action.action.delete.page.error", { diff --git a/core/page-actions/page-actions-ui/src/vue/MovePage.vue b/core/page-actions/page-actions-ui/src/vue/MovePage.vue index 474319505..49ee956ae 100644 --- a/core/page-actions/page-actions-ui/src/vue/MovePage.vue +++ b/core/page-actions/page-actions-ui/src/vue/MovePage.vue @@ -32,12 +32,9 @@ import type { import type { ModelReferenceHandler, ModelReferenceHandlerProvider, - ModelReferenceParser, - ModelReferenceParserProvider, ModelReferenceSerializer, ModelReferenceSerializerProvider, } from "@xwiki/cristal-model-reference-api"; -import type { NavigationTreeNode } from "@xwiki/cristal-navigation-tree-api"; import type { PageRenameManager, PageRenameManagerProvider, @@ -50,6 +47,7 @@ const { t } = useI18n({ const props = defineProps<{ currentPage: PageData | undefined; currentPageName: string; + currentPageReference: DocumentReference; }>(); const cristal: CristalApp = inject("cristal")!; @@ -67,10 +65,6 @@ const referenceHandler: ModelReferenceHandler = cristal .getContainer() .get("ModelReferenceHandlerProvider") .get()!; -const referenceParser: ModelReferenceParser = cristal - .getContainer() - .get("ModelReferenceParserProvider") - .get()!; const referenceSerializer: ModelReferenceSerializer = cristal .getContainer() .get("ModelReferenceSerializerProvider") @@ -79,29 +73,23 @@ const referenceSerializer: ModelReferenceSerializer = cristal const dialogOpen: Ref = ref(false); const preserveChildren: Ref = ref(true); const existingPage: Ref = ref(undefined); -let locationReference: SpaceReference | undefined = undefined; -let newDocumentReference: string; - -function treeNodeClickAction(node: NavigationTreeNode) { - locationReference = node.location; -} +const locationReference: Ref = ref(undefined); +let newDocumentReference: DocumentReference; +let newDocumentReferenceSerialized: string; async function movePage() { - const documentReference: DocumentReference = referenceParser.parse( - props.currentPageName, - ) as DocumentReference; - newDocumentReference = referenceSerializer.serialize( - referenceHandler.createDocumentReference( - documentReference.name, - locationReference!, - ), - )!; + newDocumentReference = referenceHandler.createDocumentReference( + props.currentPageReference.name, + locationReference.value!, + ); + newDocumentReferenceSerialized = + referenceSerializer.serialize(newDocumentReference)!; - existingPage.value = await cristal.getPage(newDocumentReference); + existingPage.value = await cristal.getPage(newDocumentReferenceSerialized); if (!existingPage.value) { const result = await pageRenameManager.updateReference( props.currentPage!, - newDocumentReference, + newDocumentReferenceSerialized, preserveChildren.value, ); dialogOpen.value = false; @@ -112,17 +100,19 @@ async function movePage() { async function handleSuccess(result: { success: boolean; error?: string }) { if (result.success) { if (preserveChildren.value) { - documentService.notifyDocumentChange("delete", props.currentPage!); + documentService.notifyDocumentChange( + "delete", + props.currentPageReference, + ); } - cristal.setCurrentPage(newDocumentReference, "view"); + cristal.setCurrentPage(newDocumentReferenceSerialized, "view"); alertsService.success( t("page.action.action.move.page.success", { page: props.currentPageName, - newPage: newDocumentReference, + newPage: newDocumentReferenceSerialized, }), ); - const newPage: PageData = (await cristal.getPage(newDocumentReference))!; - documentService.notifyDocumentChange("update", newPage); + documentService.notifyDocumentChange("update", newDocumentReference); } else { alertsService.error( t("page.action.action.move.page.error", { @@ -152,7 +142,7 @@ async function handleSuccess(result: { success: boolean; error?: string }) { :href=" cristal.getRouter().resolve({ name: 'view', - params: { page: newDocumentReference }, + params: { page: newDocumentReferenceSerialized }, }).href " >{{ newDocumentReference }}
-
- -
-
+ + -
+
@@ -189,11 +179,6 @@ async function handleSuccess(result: { success: boolean; error?: string }) { diff --git a/core/page-actions/page-actions-ui/src/vue/PageActions.vue b/core/page-actions/page-actions-ui/src/vue/PageActions.vue index c4554d08a..f2dc62e70 100644 --- a/core/page-actions/page-actions-ui/src/vue/PageActions.vue +++ b/core/page-actions/page-actions-ui/src/vue/PageActions.vue @@ -22,6 +22,7 @@ import PageActionsCategory from "./PageActionsCategory.vue"; import { CIcon, Size } from "@xwiki/cristal-icons"; import { inject } from "vue"; import type { CristalApp, PageData } from "@xwiki/cristal-api"; +import type { DocumentReference } from "@xwiki/cristal-model-api"; import type { PageActionCategory, PageActionCategoryService, @@ -30,6 +31,7 @@ import type { defineProps<{ currentPage: PageData | undefined; currentPageName: string; + currentPageReference: DocumentReference; disabled: boolean; }>(); @@ -52,6 +54,7 @@ const actionCategories: PageActionCategory[] = actionCategoryService.list(); :category="category" :current-page="currentPage" :current-page-name="currentPageName" + :current-page-reference="currentPageReference" :is-last="i == actionCategories.length - 1" > diff --git a/core/page-actions/page-actions-ui/src/vue/PageActionsCategory.vue b/core/page-actions/page-actions-ui/src/vue/PageActionsCategory.vue index 022e2e724..ce217873d 100644 --- a/core/page-actions/page-actions-ui/src/vue/PageActionsCategory.vue +++ b/core/page-actions/page-actions-ui/src/vue/PageActionsCategory.vue @@ -20,6 +20,7 @@ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + + + + diff --git a/ds/shoelace/src/vue/form/x-text-field.vue b/ds/shoelace/src/vue/form/x-text-field.vue index a14fdc700..6260396fc 100644 --- a/ds/shoelace/src/vue/form/x-text-field.vue +++ b/ds/shoelace/src/vue/form/x-text-field.vue @@ -11,6 +11,7 @@ const input = defineModel(); void; - const cristal: CristalApp = inject("cristal")!; const documentService: DocumentService = cristal .getContainer() @@ -61,10 +61,7 @@ const items = useTemplateRef>("items"); var selectedTreeItem: SlTreeItem | undefined; -const props = defineProps<{ - clickAction?: OnClickAction; - currentPage?: PageData; -}>(); +const props = defineProps(); onBeforeMount(async () => { rootNodes.value.push(...(await treeSource.getChildNodes(""))); @@ -73,12 +70,14 @@ onBeforeMount(async () => { documentService.registerDocumentChangeListener("update", onDocumentUpdate); }); -watch(() => props.currentPage, expandTree); +watch(() => props.currentPageReference, expandTree); watch(items, expandTree); async function expandTree() { - if (props.currentPage) { - const nodesToExpand = treeSource.getParentNodesId(props.currentPage); + if (props.currentPageReference) { + const nodesToExpand = treeSource.getParentNodesId( + props.currentPageReference, + ); if (items.value) { await Promise.all( items.value!.map(async (it) => it.expandTree(nodesToExpand)), @@ -105,7 +104,7 @@ function onSelectionChange(selection: SlTreeItem) { selectedTreeItem = selection; } -async function onDocumentDelete(page: PageData) { +async function onDocumentDelete(page: DocumentReference) { const parents = treeSource.getParentNodesId(page); for (const i of rootNodes.value.keys()) { if (rootNodes.value[i].id == parents[0]) { @@ -122,7 +121,7 @@ async function onDocumentDelete(page: PageData) { // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule. // eslint-disable-next-line max-statements -async function onDocumentUpdate(page: PageData) { +async function onDocumentUpdate(page: DocumentReference) { const parents = treeSource.getParentNodesId(page); for (const i of rootNodes.value.keys()) { diff --git a/ds/vuetify/langs/translation-en.json b/ds/vuetify/langs/translation-en.json index b6480060d..49288b4dd 100644 --- a/ds/vuetify/langs/translation-en.json +++ b/ds/vuetify/langs/translation-en.json @@ -1,4 +1,6 @@ { "vuetify.file.input.mandatory": "Mandatory field", - "vuetify.text.input.mandatory": "Mandatory field" + "vuetify.text.input.mandatory": "Mandatory field", + "vuetify.tree.select.location.label": "Change Page Location", + "vuetify.tree.select.location.select": "Select" } diff --git a/ds/vuetify/langs/translation-fr.json b/ds/vuetify/langs/translation-fr.json index 3e6ed651d..e4de9aaac 100644 --- a/ds/vuetify/langs/translation-fr.json +++ b/ds/vuetify/langs/translation-fr.json @@ -1,4 +1,6 @@ { "vuetify.file.input.mandatory": "Champ obligatoire", - "vuetify.text.input.mandatory": "Champ obligatoire" + "vuetify.text.input.mandatory": "Champ obligatoire", + "vuetify.tree.select.location.label": "Changer l'emplacement de la page", + "vuetify.tree.select.location.select": "Sélectionner" } diff --git a/ds/vuetify/package.json b/ds/vuetify/package.json index c1e47a398..f464378fa 100644 --- a/ds/vuetify/package.json +++ b/ds/vuetify/package.json @@ -28,7 +28,9 @@ "dependencies": { "@xwiki/cristal-api": "workspace:*", "@xwiki/cristal-document-api": "workspace:*", + "@xwiki/cristal-hierarchy-api": "workspace:*", "@xwiki/cristal-model-api": "workspace:*", + "@xwiki/cristal-model-reference-api": "workspace:*", "@xwiki/cristal-navigation-tree-api": "workspace:*", "inversify": "6.2.2", "vue": "3.5.13", diff --git a/ds/vuetify/src/components/vuetifyDesignSystemLoader.ts b/ds/vuetify/src/components/vuetifyDesignSystemLoader.ts index 11692f747..ef952e022 100644 --- a/ds/vuetify/src/components/vuetifyDesignSystemLoader.ts +++ b/ds/vuetify/src/components/vuetifyDesignSystemLoader.ts @@ -147,5 +147,10 @@ export class VuetifyDesignSystemLoader implements DesignSystemLoader { "XCheckbox", () => import("../vue/form/x-checkbox.vue"), ); + registerAsyncComponent( + app, + "XNavigationTreeSelect", + () => import("../vue/form/x-navigation-tree-select.vue"), + ); } } diff --git a/ds/vuetify/src/translations.ts b/ds/vuetify/src/translations.ts index ba75b08f2..16d67865e 100644 --- a/ds/vuetify/src/translations.ts +++ b/ds/vuetify/src/translations.ts @@ -18,9 +18,13 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ +import de from "../langs/translation-de.json"; import en from "../langs/translation-en.json"; +import fr from "../langs/translation-fr.json"; const translations: Record> = { en, + fr, + de, }; export default translations; diff --git a/ds/vuetify/src/vue/form/x-form.vue b/ds/vuetify/src/vue/form/x-form.vue index 6457f7522..5f7f024fb 100644 --- a/ds/vuetify/src/vue/form/x-form.vue +++ b/ds/vuetify/src/vue/form/x-form.vue @@ -8,10 +8,14 @@ function submit() { - + diff --git a/ds/vuetify/src/vue/form/x-navigation-tree-select.vue b/ds/vuetify/src/vue/form/x-navigation-tree-select.vue new file mode 100644 index 000000000..1f31150fe --- /dev/null +++ b/ds/vuetify/src/vue/form/x-navigation-tree-select.vue @@ -0,0 +1,120 @@ + + + + + + diff --git a/ds/vuetify/src/vue/form/x-text-field.vue b/ds/vuetify/src/vue/form/x-text-field.vue index e23b40401..4b9638aa6 100644 --- a/ds/vuetify/src/vue/form/x-text-field.vue +++ b/ds/vuetify/src/vue/form/x-text-field.vue @@ -32,6 +32,8 @@ const rules = computed(() => { :label="label" :name="name" :autofocus="autofocus" + :hint="help" + :persistent-hint="help !== undefined" :rules="rules" > diff --git a/ds/vuetify/src/vue/x-navigation-tree.vue b/ds/vuetify/src/vue/x-navigation-tree.vue index cb1ab6e67..c3a89f7cc 100644 --- a/ds/vuetify/src/vue/x-navigation-tree.vue +++ b/ds/vuetify/src/vue/x-navigation-tree.vue @@ -31,11 +31,14 @@ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA */ import { inject, onBeforeMount, ref, watch } from "vue"; import { VTreeview } from "vuetify/labs/VTreeview"; -import type { CristalApp, PageData } from "@xwiki/cristal-api"; +import type { CristalApp } from "@xwiki/cristal-api"; import type { DocumentService } from "@xwiki/cristal-document-api"; -import type { SpaceReference } from "@xwiki/cristal-model-api"; +import type { NavigationTreeProps } from "@xwiki/cristal-dsapi"; +import type { + DocumentReference, + SpaceReference, +} from "@xwiki/cristal-model-api"; import type { - NavigationTreeNode, NavigationTreeSource, NavigationTreeSourceProvider, } from "@xwiki/cristal-navigation-tree-api"; @@ -49,8 +52,6 @@ type TreeItem = { _location: SpaceReference; }; -type OnClickAction = (node: NavigationTreeNode) => void; - const cristal: CristalApp = inject("cristal")!; const documentService: DocumentService = cristal .getContainer() @@ -66,10 +67,7 @@ const activatedNodes: Ref> = ref(new Array()); const expandedNodes: Ref> = ref(new Array()); var isExpanding: boolean = false; -const props = defineProps<{ - clickAction?: OnClickAction; - currentPage?: PageData; -}>(); +const props = defineProps(); onBeforeMount(async () => { for (const node of await treeSource.getChildNodes("")) { @@ -87,14 +85,16 @@ onBeforeMount(async () => { documentService.registerDocumentChangeListener("update", onDocumentUpdate); }); -watch(() => props.currentPage, expandTree); +watch(() => props.currentPageReference, expandTree); // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule. // eslint-disable-next-line max-statements async function expandTree() { - if (props.currentPage && !isExpanding) { + if (props.currentPageReference && !isExpanding) { isExpanding = true; - const newExpandedNodes = treeSource.getParentNodesId(props.currentPage); + const newExpandedNodes = treeSource.getParentNodesId( + props.currentPageReference, + ); let i; let currentNodes = rootNodes.value; for (i = 0; i < newExpandedNodes.length - 1; i++) { @@ -163,7 +163,7 @@ function clearSelection() { // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule. // eslint-disable-next-line max-statements -async function onDocumentDelete(page: PageData) { +async function onDocumentDelete(page: DocumentReference) { const parents = treeSource.getParentNodesId(page); let currentItem: TreeItem | undefined = undefined; let currentItemChildren: TreeItem[] | undefined = rootNodes.value; @@ -192,7 +192,7 @@ async function onDocumentDelete(page: PageData) { // TODO: reduce the number of statements in the following method and reactivate the disabled eslint rule. // eslint-disable-next-line max-statements -async function onDocumentUpdate(page: PageData) { +async function onDocumentUpdate(page: DocumentReference) { const parents = treeSource.getParentNodesId(page); let currentParent: string | undefined = undefined; let currentItems: TreeItem[] | undefined = rootNodes.value; @@ -238,7 +238,9 @@ async function onDocumentUpdate(page: PageData) { const newItems = await treeSource.getChildNodes( currentParent ? currentParent : "", ); - const currentPageParents = treeSource.getParentNodesId(props.currentPage); + const currentPageParents = treeSource.getParentNodesId( + props.currentPageReference, + ); newItemsLoop: for (const newItem of newItems) { for (const i of currentItems!.keys()) { if (newItem.id == currentItems![i].id) { diff --git a/editors/tiptap/src/vue/c-edit-tiptap.vue b/editors/tiptap/src/vue/c-edit-tiptap.vue index 06e3b1b3f..cbe15ffa0 100644 --- a/editors/tiptap/src/vue/c-edit-tiptap.vue +++ b/editors/tiptap/src/vue/c-edit-tiptap.vue @@ -68,6 +68,7 @@ import type { LinkSuggestService, LinkSuggestServiceProvider, } from "@xwiki/cristal-link-suggest-api"; +import type { DocumentReference } from "@xwiki/cristal-model-api"; import type { Markdown } from "tiptap-markdown"; import type { Ref } from "vue"; @@ -94,13 +95,15 @@ const loading = documentService.isLoading(); const error: Ref = documentService.getError(); const currentPage: Ref = documentService.getCurrentDocument(); +const currentPageReference: Ref = + documentService.getCurrentDocumentReference(); // TODO: load this content first, then initialize the editor. // Make the loading status first. const content = ref(""); const title = ref(""); const titlePlaceholder = modelReferenceHandler?.getTitle( - documentService.getCurrentDocumentReference().value!, + currentPageReference.value!, ); const hasRealtime = cristal.getWikiConfig().realtimeURL != undefined; @@ -158,7 +161,7 @@ const save = async () => { if (!currentPage.value) { documentService.setCurrentDocument(currentPageName.value ?? ""); } - documentService.notifyDocumentChange("update", currentPage.value!); + documentService.notifyDocumentChange("update", currentPageReference.value!); }; const submit = async () => { if (!hasRealtime) { @@ -336,6 +339,7 @@ watch( :loading="loading" :error="error" :current-page="currentPage" + :current-page-reference="currentPageReference" :page-exist="true" before-u-i-x-p-id="edit.before" after-u-i-x-p-id="edit.after" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f76454c39..24ac92d79 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -950,6 +950,9 @@ importers: '@xwiki/cristal-api': specifier: workspace:* version: link:../../../api + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../../model/model-api devDependencies: '@xwiki/cristal-dev-config': specifier: workspace:* @@ -969,6 +972,9 @@ importers: '@xwiki/cristal-hierarchy-api': specifier: workspace:* version: link:../hierarchy-api + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../../model/model-api inversify: specifier: 6.2.2 version: 6.2.2(reflect-metadata@0.2.2) @@ -997,6 +1003,9 @@ importers: '@xwiki/cristal-hierarchy-default': specifier: workspace:* version: link:../hierarchy-default + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../../model/model-api inversify: specifier: 6.2.2 version: 6.2.2(reflect-metadata@0.2.2) @@ -1022,6 +1031,9 @@ importers: '@xwiki/cristal-hierarchy-api': specifier: workspace:* version: link:../hierarchy-api + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../../model/model-api inversify: specifier: 6.2.2 version: 6.2.2(reflect-metadata@0.2.2) @@ -1050,6 +1062,9 @@ importers: '@xwiki/cristal-hierarchy-default': specifier: workspace:* version: link:../hierarchy-default + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../../model/model-api inversify: specifier: 6.2.2 version: 6.2.2(reflect-metadata@0.2.2) @@ -1851,6 +1866,9 @@ importers: '@xwiki/cristal-api': specifier: workspace:* version: link:../../../api + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../../model/model-api '@xwiki/cristal-navigation-tree-api': specifier: workspace:* version: link:../navigation-tree-api @@ -2436,6 +2454,9 @@ importers: '@xwiki/cristal-api': specifier: workspace:* version: link:../../api + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../../core/model/model-api '@xwiki/cristal-navigation-tree-api': specifier: workspace:* version: link:../../core/navigation-tree/navigation-tree-api @@ -2504,9 +2525,18 @@ importers: '@xwiki/cristal-document-api': specifier: workspace:* version: link:../../core/document/document-api + '@xwiki/cristal-hierarchy-api': + specifier: workspace:* + version: link:../../core/hierarchy/hierarchy-api '@xwiki/cristal-icons': specifier: workspace:* version: link:../../core/icons + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../../core/model/model-api + '@xwiki/cristal-model-reference-api': + specifier: workspace:* + version: link:../../core/model/model-reference/model-reference-api '@xwiki/cristal-navigation-tree-api': specifier: workspace:* version: link:../../core/navigation-tree/navigation-tree-api @@ -2516,6 +2546,9 @@ importers: vue: specifier: 3.5.13 version: 3.5.13(typescript@5.7.3) + vue-i18n: + specifier: 11.1.0 + version: 11.1.0(vue@3.5.13(typescript@5.7.3)) devDependencies: '@vue/runtime-dom': specifier: 3.5.13 @@ -2550,9 +2583,15 @@ importers: '@xwiki/cristal-document-api': specifier: workspace:* version: link:../../core/document/document-api + '@xwiki/cristal-hierarchy-api': + specifier: workspace:* + version: link:../../core/hierarchy/hierarchy-api '@xwiki/cristal-model-api': specifier: workspace:* version: link:../../core/model/model-api + '@xwiki/cristal-model-reference-api': + specifier: workspace:* + version: link:../../core/model/model-reference/model-reference-api '@xwiki/cristal-navigation-tree-api': specifier: workspace:* version: link:../../core/navigation-tree/navigation-tree-api diff --git a/skin/langs/translation-en.json b/skin/langs/translation-en.json index 263524d93..d45226b25 100644 --- a/skin/langs/translation-en.json +++ b/skin/langs/translation-en.json @@ -6,7 +6,10 @@ "page.creation.menu.alert.content.edit.link": "edit the existing page", "page.creation.menu.button": "New Page", "page.creation.menu.field.location": "Parent location", + "page.creation.menu.field.location.help": "Parent for the new page", "page.creation.menu.field.name": "Name", + "page.creation.menu.field.name.help": "Name of the new page", + "page.creation.menu.cancel": "Cancel", "page.creation.menu.submit": "Create", "page.creation.menu.title": "New Page", "page.edited.details": "Edited on {date}", diff --git a/skin/langs/translation-fr.json b/skin/langs/translation-fr.json index ff5260256..117ccddfa 100644 --- a/skin/langs/translation-fr.json +++ b/skin/langs/translation-fr.json @@ -6,7 +6,10 @@ "page.creation.menu.alert.content.edit.link": "éditez la page existante", "page.creation.menu.button": "Nouvelle page", "page.creation.menu.field.location": "Emplacement du parent", + "page.creation.menu.field.location.help": "Parent de la nouvelle page", "page.creation.menu.field.name": "Nom", + "page.creation.menu.field.name.help": "Nom de la nouvelle page", + "page.creation.menu.cancel": "Annuler", "page.creation.menu.submit": "Créer", "page.creation.menu.title": "Nouvelle page", "page.edited.details": "Édité le {date}", diff --git a/skin/src/vue/__tests__/c-content.test.ts b/skin/src/vue/__tests__/c-content.test.ts index 27c03eb3a..6877b2996 100644 --- a/skin/src/vue/__tests__/c-content.test.ts +++ b/skin/src/vue/__tests__/c-content.test.ts @@ -32,6 +32,7 @@ import { PageHierarchyResolverProvider, } from "@xwiki/cristal-hierarchy-api"; import { MarkdownRenderer } from "@xwiki/cristal-markdown-api"; +import { DocumentReference, SpaceReference } from "@xwiki/cristal-model-api"; import { ClickListener } from "@xwiki/cristal-model-click-listener"; import { Container } from "inversify"; import { DeepPartial } from "ts-essentials"; @@ -74,6 +75,14 @@ function mountCComponent(params: { return ref(undefined); } + getCurrentDocumentReference() { + return ref( + new DocumentReference( + "Reference", + new SpaceReference(undefined, "The", "Page"), + ), + ); + } getCurrentDocumentReferenceString() { return ref("The.Page.Reference"); } diff --git a/skin/src/vue/c-article.vue b/skin/src/vue/c-article.vue index d01e325e5..df0caf59d 100644 --- a/skin/src/vue/c-article.vue +++ b/skin/src/vue/c-article.vue @@ -15,16 +15,18 @@ import type { PageHierarchyItem, PageHierarchyResolverProvider, } from "@xwiki/cristal-hierarchy-api"; +import type { DocumentReference } from "@xwiki/cristal-model-api"; const { t } = useI18n({ messages, }); const cristal: CristalApp = inject("cristal")!; -const { currentPage } = defineProps<{ +const { currentPage, currentPageReference } = defineProps<{ loading: boolean; error: Error | undefined; currentPage: PageData | undefined; + currentPageReference: DocumentReference | undefined; pageExist: boolean; beforeUIXPId: string; afterUIXPId: string; @@ -32,7 +34,7 @@ const { currentPage } = defineProps<{ const breadcrumbItems: Ref> = ref([]); watch( - () => currentPage, + () => currentPageReference, async (p) => { if (p) { try { diff --git a/skin/src/vue/c-content.vue b/skin/src/vue/c-content.vue index d25728405..a15764de6 100644 --- a/skin/src/vue/c-content.vue +++ b/skin/src/vue/c-content.vue @@ -48,6 +48,7 @@ const currentPage: Ref = const currentPageRevision: Ref = documentService.getCurrentDocumentRevision(); const currentPageName = documentService.getCurrentDocumentReferenceString(); +const currentPageReference = documentService.getCurrentDocumentReference(); const displayTitle = documentService.getDisplayTitle(); const contentRoot = ref(undefined); @@ -85,6 +86,7 @@ onUpdated(() => { :loading="loading" :error="error" :current-page="currentPage" + :current-page-reference="currentPageReference" :page-exist="pageExist" before-u-i-x-p-id="content.before" after-u-i-x-p-id="content.after" @@ -108,6 +110,7 @@ onUpdated(() => { diff --git a/skin/src/vue/c-page-creation-menu.vue b/skin/src/vue/c-page-creation-menu.vue index f718b686e..867f236e6 100644 --- a/skin/src/vue/c-page-creation-menu.vue +++ b/skin/src/vue/c-page-creation-menu.vue @@ -20,17 +20,19 @@ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA