Skip to content

Commit

Permalink
CRISTAL-83: Basic Move/Rename is possible
Browse files Browse the repository at this point in the history
* Improve documentation
* Use classes instead of ids when possible
* Remove !important
* Handle move edge cases when preserving children
  • Loading branch information
pjeanjean committed Jan 24, 2025
1 parent f32dd1c commit 2f954e2
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 44 deletions.
4 changes: 2 additions & 2 deletions api/src/api/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ export interface Storage {
*
* @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
* @param preserveChildren - whether to move children
* @returns true if the move was successful, false with the reason otherwise
*
* @since 0.14
*/
Expand Down
4 changes: 2 additions & 2 deletions core/backends/backend-api/src/abstractStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ export abstract class AbstractStorage implements Storage {
*
* @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
* @param preserveChildren - whether to move children
* @returns true if the move was successful, false with the reason otherwise
*
* @since 0.14
*/
Expand Down
4 changes: 2 additions & 2 deletions core/rename/rename-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ interface PageRenameManager {
*
* @param pageData - the page for which to get the revisions
* @param newReference - the new reference for the page
* @param preserveChildren - whether or not to also affect children
* @param preserveChildren - whether to also affect children
* @returns true if this was successful, false with the reason otherwise
*/
updateReference(
Expand All @@ -44,7 +44,7 @@ interface PageRenameManager {
}

/**
* A PageRenameManagerProvider returns the instance of PageRenameManager
* A PageRenameManagerProvider returns the instance of {@link PageRenameManager}
* matching the current wiki configuration.
*
* @since 0.14
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class FileSystemPageRenameManager implements PageRenameManager {
*
* @param pageData - the page for which to get the revisions
* @param newReference - the new reference for the page
* @param preserveChildren - whether or not to also affect children
* @param preserveChildren - whether to also affect children
* @returns true if this was successful, false with the reason otherwise
*/
async updateReference(
Expand Down
4 changes: 2 additions & 2 deletions ds/shoelace/src/vue/x-dialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const open = defineModel<boolean>();
and will hold the contents of our own footer slot (if any). -->
<!-- @vue-expect-error the slot attribute is shoelace specific and is not know by the typechecker.
Disabling it for now as I did not find an elegant solution to declare this property. -->
<div v-if="$slots.footer" id="footer" slot="footer">
<div v-if="$slots.footer" slot="footer" class="footer">
<slot name="footer" />
</div>
</sl-dialog>
Expand All @@ -65,7 +65,7 @@ sl-dialog {
--width: v-bind(width);
--body-spacing: 0 1.25rem 1.25rem;
}
#footer {
.footer {
display: flex;
justify-content: flex-end;
gap: var(--cr-spacing-x-small);
Expand Down
11 changes: 6 additions & 5 deletions ds/shoelace/src/vue/x-navigation-tree-item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,12 @@ async function onDocumentUpdate(parents: string[]) {
>
<a
v-if="props.clickAction"
class="undecorated"
:href="node.url"
@click.prevent="onClick(node)"
>{{ node.label }}</a
>
<a v-else :href="node.url">{{ node.label }}</a>
<a v-else :href="node.url" class="undecorated">{{ node.label }}</a>
<!-- @vue-expect-error the slot attribute is shoelace specific and is not know by the typechecker.
Disabling it for now as I did not find an elegant solution to declare this property. -->
<x-navigation-tree-item
Expand All @@ -199,12 +200,12 @@ async function onDocumentUpdate(parents: string[]) {
</template>

<style scoped>
:deep(a) {
text-decoration: none !important;
color: var(--cr-base-text-color) !important;
sl-tree-item > a.undecorated {
text-decoration: none;
color: var(--cr-base-text-color);
}
/* Disable hand cursor on items, since we disable the default click action. */
:deep(sl-tree-item)::part(base) {
sl-tree-item::part(base) {
cursor: default;
}
</style>
11 changes: 1 addition & 10 deletions ds/shoelace/src/vue/x-navigation-tree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,4 @@ async function onDocumentUpdate(page: PageData) {
</sl-tree>
</template>

<style scoped>
:deep(a) {
text-decoration: none !important;
color: var(--cr-base-text-color) !important;
}
/* Disable hand cursor on items, since we disable the default click action. */
:deep(sl-tree-item)::part(base) {
cursor: default;
}
</style>
<style scoped></style>
2 changes: 1 addition & 1 deletion ds/vuetify/src/vue/form/x-checkbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ defineProps<CheckboxProps>();
const checked = defineModel<boolean>();
</script>
<template>
<v-checkbox v-model="checked" :label="label" :messages="help"> </v-checkbox>
<v-checkbox v-model="checked" :label="label" :messages="help"></v-checkbox>
</template>

<style scoped></style>
105 changes: 87 additions & 18 deletions electron/storage/src/electron/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,28 +364,97 @@ async function movePage(
newPath: string,
preserveChildren: boolean,
): Promise<void> {
const directory = dirname(path);
const newDirectory = dirname(newPath);

if (preserveChildren) {
await fs.promises.mkdir(dirname(newDirectory), { recursive: true });
await fs.promises.rename(directory, newDirectory);
const directory = dirname(path);
const newDirectory = dirname(newPath);

const success: boolean = await movePageDeep(directory, newDirectory);
if (!success) {
throw "Some child pages were not moved because they overlapped with children of the target.";
}
} else {
await fs.promises.mkdir(newDirectory);
await fs.promises.rename(path, newPath);

if (await isDirectory(`${directory}/attachments`)) {
await fs.promises.rename(
`${directory}/attachments`,
`${newDirectory}/attachments`,
);
await movePageSingle(path, newPath);
}
}

async function movePageDeep(
directory: string,
newDirectory: string,
): Promise<boolean> {
let success = true;

// We start by removing the directory from the arborescence.
const tempPath = resolvePath(`temp-${Math.random().toString(16).slice(2)}`);
await fs.promises.rename(directory, tempPath);

await fs.promises.mkdir(dirname(newDirectory), { recursive: true });

if (await pathExists(newDirectory)) {
// If the target directory already exists, we move the content instead.
success = await movePageDeepRecursive(tempPath, newDirectory);
// We put the (possible) unmoved content back to where it was.
await fs.promises.rename(tempPath, directory);
} else {
await fs.promises.rename(tempPath, newDirectory);
}
await cleanEmptyArborescence(directory);

return success;
}

async function movePageDeepRecursive(
directory: string,
newDirectory: string,
): Promise<boolean> {
let success: boolean = true;

for (const file of await fs.promises.readdir(directory)) {
const filePath = join(directory, file);
const newFilePath = join(newDirectory, file);
if (await isDirectory(filePath)) {
success =
success && (await movePageDeep(filePath, join(newDirectory, file)));
await cleanEmptyArborescence(filePath);
} else if (!(await pathExists(newFilePath))) {
await fs.promises.rename(filePath, newFilePath);
} else {
success = false;
}
}

fs.promises.readdir(directory).then(async (files) => {
if (files.length === 0) {
await fs.promises.rmdir(directory);
}
});
return success;
}

async function movePageSingle(path: string, newPath: string) {
const directory = dirname(path);
const newDirectory = dirname(newPath);

await fs.promises.mkdir(newDirectory, { recursive: true });
await fs.promises.rename(path, newPath);

if (await isDirectory(`${directory}/attachments`)) {
await fs.promises.rename(
`${directory}/attachments`,
`${newDirectory}/attachments`,
);
}

await cleanEmptyArborescence(directory);
}

/**
* Recursively delete empty directories, starting from the leaf.
* @param directory - the starting leaf directory
* @since 0.14
*/
async function cleanEmptyArborescence(directory: string): Promise<void> {
if (await isWithin(HOME_PATH_FULL, directory)) {
if (!(await pathExists(directory))) {
await cleanEmptyArborescence(dirname(directory));
} else if ((await isDirectory(directory)) && (await isEmpty(directory))) {
await fs.promises.rmdir(directory);
await cleanEmptyArborescence(dirname(directory));
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion electron/storage/src/electron/preload/apiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export interface APITypes {
*
* @param path - the path to the page to move
* @param newPath - the new path for the page
* @param preserveChildren - whether or not to move children
* @param preserveChildren - whether to move children
*
* @since 0.14
*/
Expand Down

0 comments on commit 2f954e2

Please sign in to comment.