Skip to content

Commit

Permalink
CRISTAL-83: Basic Move/Rename is possible
Browse files Browse the repository at this point in the history
* Introduce PageRenameManager
* Implement PageRenameManager for FileSystem
* Add move() operation to storage
* Add move and rename page actions
* Small improvements to the navigation trees
  • Loading branch information
pjeanjean committed Jan 24, 2025
1 parent d1201ae commit f32dd1c
Show file tree
Hide file tree
Showing 54 changed files with 1,407 additions and 25 deletions.
16 changes: 16 additions & 0 deletions api/src/api/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,20 @@ export interface Storage {
* @since 0.11
*/
delete(page: string): Promise<{ success: boolean; error?: string }>;

/**
* Move a page.
*
* @param page - the page to move
* @param newPage - the new location for the page
* @param preserveChildren - whether or not to move children
* @returns true if the delete was successful, false with the reason otherwise
*
* @since 0.14
*/
move(
page: string,
newPage: string,
preserveChildren: boolean,
): Promise<{ success: boolean; error?: string }>;
}
16 changes: 16 additions & 0 deletions core/backends/backend-api/src/abstractStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,20 @@ export abstract class AbstractStorage implements Storage {
* @since 0.11
*/
abstract delete(page: string): Promise<{ success: boolean; error?: string }>;

/**
* Move a page.
*
* @param page - the page to move
* @param newPage - the new location for the page
* @param preserveChildren - whether or not to move children
* @returns true if the delete was successful, false with the reason otherwise
*
* @since 0.14
*/
abstract move(
page: string,
newPage: string,
preserveChildren: boolean,
): Promise<{ success: boolean; error?: string }>;
}
8 changes: 8 additions & 0 deletions core/backends/backend-dexie/src/wrappingOfflineStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,12 @@ export class WrappingOfflineStorage implements WrappingStorage {
async delete(page: string): Promise<{ success: boolean; error?: string }> {
return this.storage.delete(page);
}

async move(
page: string,
newPage: string,
preserveChildren: boolean,
): Promise<{ success: boolean; error?: string }> {
return this.storage.move(page, newPage, preserveChildren);
}
}
5 changes: 5 additions & 0 deletions core/backends/backend-github/src/githubStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,9 @@ export class GitHubStorage extends AbstractStorage {
// TODO: to be implemented
throw new Error("Delete not supported");
}

async move(): Promise<{ success: boolean; error?: string }> {
// TODO: to be implemented
throw new Error("Move not supported");
}
}
5 changes: 5 additions & 0 deletions core/backends/backend-nextcloud/src/nextcloudStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ export class NextcloudStorage extends AbstractStorage {
return success;
}

async move(): Promise<{ success: boolean; error?: string }> {
// TODO: to be implemented
throw new Error("Move not supported");
}

async isStorageReady(): Promise<boolean> {
return true;
}
Expand Down
5 changes: 5 additions & 0 deletions core/backends/backend-xwiki/src/xwikiStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,11 @@ export class XWikiStorage extends AbstractStorage {
return success;
}

async move(): Promise<{ success: boolean; error?: string }> {
// TODO: to be implemented
throw new Error("Move not supported");
}

private async getCredentials(): Promise<{ Authorization?: string }> {
const authorizationHeader = await this.authenticationManagerProvider
.get()
Expand Down
8 changes: 7 additions & 1 deletion core/page-actions/page-actions-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ interface PageAction {
*/
order: number;

/**
* Compute if the page action should be displayed.
* @since 0.14
*/
enabled(): Promise<boolean>;

/**
* Get a Vue component for this action.
* The component should handle the following props:
Expand All @@ -97,7 +103,7 @@ interface PageActionService {
*
* @param categoryId - the id of the category
*/
list(categoryId: string): PageAction[];
list(categoryId: string): Promise<PageAction[]>;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import { PageAction, PageActionService } from "@xwiki/cristal-page-actions-api";

import { injectable, multiInject } from "inversify";
import { filter, sortBy } from "lodash";
import { sortBy } from "lodash";

/**
* @since 0.11
Expand All @@ -30,11 +30,20 @@ import { filter, sortBy } from "lodash";
class DefaultPageActionService implements PageActionService {
constructor(
@multiInject("PageAction")
private action: PageAction[],
private actions: PageAction[],
) {}

list(categoryId: string): PageAction[] {
return sortBy(filter(this.action, { categoryId: categoryId }), ["order"]);
async list(categoryId: string): Promise<PageAction[]> {
const enabledActions: boolean[] = await Promise.all(
this.actions.map(
async (action) =>
(await action.enabled()) && action.categoryId == categoryId,
),
);
return sortBy(
this.actions.filter((_, i) => enabledActions[i]),
["order"],
);
}
}

Expand Down
18 changes: 17 additions & 1 deletion core/page-actions/page-actions-ui/langs/translation-en.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
{
"page.action.category.page.management.title": "Manage",
"page.action.action.delete.page.title": "Delete",
"page.action.action.delete.page.cancel": "Cancel",
"page.action.action.delete.page.dialog.title": "Delete Page",
"page.action.action.delete.page.success": "The page \"{page}\" has been deleted.",
"page.action.action.delete.page.error": "Failed to delete the page \"{page}\". Reason: [{reason}]",
"page.action.action.delete.page.confirm": "This operation will delete the page \"{page}\". Are you sure?"
"page.action.action.delete.page.confirm": "This operation will delete the page \"{page}\". Are you sure?",
"page.action.action.move.page.title": "Move",
"page.action.action.move.page.cancel": "Cancel",
"page.action.action.move.page.dialog.title": "Move Page",
"page.action.action.move.page.alert.content": "The page {pageName} already exists.",
"page.action.action.move.page.preserve.children.label": "Preserve Children",
"page.action.action.move.page.preserve.children.help": "Preserve the child pages by updating their paths and moving them to the new location.",
"page.action.action.move.page.success": "The page \"{page}\" has been moved to \"{newPage}\".",
"page.action.action.move.page.error": "Failed to move the page \"{page}\". Reason: [{reason}]",
"page.action.action.rename.page.title": "Rename",
"page.action.action.rename.page.cancel": "Cancel",
"page.action.action.rename.page.dialog.title": "Rename Page",
"page.action.action.rename.page.alert.content": "The page {pageName} already exists.",
"page.action.action.rename.page.name.label": "Name",
"page.action.action.rename.page.success": "The page \"{page}\" has been renamed to \"{newPage}\".",
"page.action.action.rename.page.error": "Failed to rename the page \"{page}\". Reason: [{reason}]"
}
18 changes: 17 additions & 1 deletion core/page-actions/page-actions-ui/langs/translation-fr.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
{
"page.action.category.page.management.title": "Gérer",
"page.action.action.delete.page.title": "Supprimer",
"page.action.action.delete.page.cancel": "Annuler",
"page.action.action.delete.page.dialog.title": "Supprimer la page",
"page.action.action.delete.page.success": "La page \"{page}\" a été supprimée.",
"page.action.action.delete.page.error": "La suppression de la page \"{page}\" a échoué. Raison : [{reason}]",
"page.action.action.delete.page.confirm": "Cette opération supprimera la page \"{page}\". Voulez-vous continuer ?"
"page.action.action.delete.page.confirm": "Cette opération supprimera la page \"{page}\". Voulez-vous continuer ?",
"page.action.action.move.page.title": "Déplacer",
"page.action.action.move.page.cancel": "Annuler",
"page.action.action.move.page.dialog.title": "Déplacer la page",
"page.action.action.move.page.alert.content": "La page {pageName} existe déjà.",
"page.action.action.move.page.preserve.children.label": "Préserver les enfants",
"page.action.action.move.page.preserve.children.help": "Préserver les pages enfants en mettant à jour leurs chemins et en les déplaçant vers le nouvel emplacement.",
"page.action.action.move.page.success": "La page \"{page}\" a été déplacée vers \"{newPage}\".",
"page.action.action.move.page.error": "Le déplacement de la page \"{page}\" a échoué. Raison : [{reason}]",
"page.action.action.rename.page.title": "Renommer",
"page.action.action.rename.page.cancel": "Annuler",
"page.action.action.rename.page.dialog.title": "Renommer la page",
"page.action.action.rename.page.alert.content": "La page {pageName} existe déjà.",
"page.action.action.rename.page.name.label": "Nom",
"page.action.action.rename.page.success": "La page \"{page}\" a été renommée en \"{newPage}\".",
"page.action.action.rename.page.error": "Le renommage de la page \"{page}\" a échoué. Raison : [{reason}]"
}
4 changes: 4 additions & 0 deletions core/page-actions/page-actions-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@
"@xwiki/cristal-document-api": "workspace:*",
"@xwiki/cristal-hierarchy-api": "workspace:*",
"@xwiki/cristal-icons": "workspace:*",
"@xwiki/cristal-model-api": "workspace:*",
"@xwiki/cristal-model-reference-api": "workspace:*",
"@xwiki/cristal-navigation-tree-api": "workspace:*",
"@xwiki/cristal-page-actions-api": "workspace:*",
"@xwiki/cristal-rename-api": "workspace:*",
"inversify": "6.2.1",
"vue": "3.5.13",
"vue-i18n": "11.0.1"
Expand Down
57 changes: 53 additions & 4 deletions core/page-actions/page-actions-ui/src/PageManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
*/

import messages from "./translations";
import DeletePage from "./vue/DeletePage.vue";
import { AbstractPageActionCategory } from "@xwiki/cristal-page-actions-api";
import { injectable } from "inversify";
import { inject, injectable } from "inversify";
import type { PageAction } from "@xwiki/cristal-page-actions-api";
import type { PageRenameManagerProvider } from "@xwiki/cristal-rename-api";
import type { Component } from "vue";

const PAGE_MANAGEMENT_ID: string = "page-management";
Expand All @@ -38,15 +38,64 @@ class PageManagementActionCategory extends AbstractPageActionCategory {
order = 1000;
}

@injectable()
class PageMoveAction implements PageAction {
constructor(
@inject("PageRenameManagerProvider")
private readonly pageRenameManagerProvider: PageRenameManagerProvider,
) {}

id = "page-move";
categoryId: string = PAGE_MANAGEMENT_ID;
order = 3000;

async enabled(): Promise<boolean> {
return this.pageRenameManagerProvider.has();
}

async component(): Promise<Component> {
return (await import("./vue/MovePage.vue")).default;
}
}

@injectable()
class PageRenameAction implements PageAction {
constructor(
@inject("PageRenameManagerProvider")
private readonly pageRenameManagerProvider: PageRenameManagerProvider,
) {}

id = "page-rename";
categoryId: string = PAGE_MANAGEMENT_ID;
order = 4000;

async enabled(): Promise<boolean> {
return this.pageRenameManagerProvider.has();
}

async component(): Promise<Component> {
return (await import("./vue/RenamePage.vue")).default;
}
}

@injectable()
class PageDeleteAction implements PageAction {
id = "page-delete";
categoryId: string = PAGE_MANAGEMENT_ID;
order = 5000;

async enabled(): Promise<boolean> {
return true;
}

async component(): Promise<Component> {
return DeletePage;
return (await import("./vue/DeletePage.vue")).default;
}
}

export { PageDeleteAction, PageManagementActionCategory };
export {
PageDeleteAction,
PageManagementActionCategory,
PageMoveAction,
PageRenameAction,
};
10 changes: 10 additions & 0 deletions core/page-actions/page-actions-ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import {
PageDeleteAction,
PageManagementActionCategory,
PageMoveAction,
PageRenameAction,
} from "./PageManagement";
import PageActions from "./vue/PageActions.vue";
import type {
Expand All @@ -35,6 +37,14 @@ class ComponentInit {
.bind<PageActionCategory>("PageActionCategory")
.to(PageManagementActionCategory)
.whenTargetIsDefault();
container
.bind<PageAction>("PageAction")
.to(PageMoveAction)
.whenTargetIsDefault();
container
.bind<PageAction>("PageAction")
.to(PageRenameAction)
.whenTargetIsDefault();
container
.bind<PageAction>("PageAction")
.to(PageDeleteAction)
Expand Down
7 changes: 6 additions & 1 deletion core/page-actions/page-actions-ui/src/vue/DeletePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,12 @@ async function deletePage() {
t("page.action.action.delete.page.confirm", { page: currentPageName })
}}
</p>
<x-btn @click.stop="deletePage">
</template>
<template #footer>
<x-btn @click.stop="deleteDialogOpen = false">
{{ t("page.action.action.delete.page.cancel") }}
</x-btn>
<x-btn variant="primary" @click.stop="deletePage">
{{ t("page.action.action.delete.page.title") }}
</x-btn>
</template>
Expand Down
Loading

0 comments on commit f32dd1c

Please sign in to comment.