2020-02-18 07:57:00 +00:00
|
|
|
<template>
|
2022-07-12 08:55:28 +00:00
|
|
|
<div class="container mx-auto" v-if="resource">
|
2022-01-10 14:19:16 +00:00
|
|
|
<breadcrumbs-nav :links="breadcrumbLinks">
|
|
|
|
<li>
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-dropdown aria-role="list">
|
|
|
|
<template #trigger>
|
|
|
|
<o-button variant="primary">+</o-button>
|
|
|
|
</template>
|
2022-01-10 14:19:16 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-dropdown-item aria-role="listitem" @click="createFolderModal">
|
|
|
|
<Folder />
|
2022-01-10 14:19:16 +00:00
|
|
|
{{ $t("New folder") }}
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-dropdown-item>
|
|
|
|
<o-dropdown-item aria-role="listitem" @click="createLinkModal">
|
|
|
|
<Link />
|
2022-01-10 14:19:16 +00:00
|
|
|
{{ $t("New link") }}
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-dropdown-item>
|
2022-01-10 14:19:16 +00:00
|
|
|
<hr
|
|
|
|
role="presentation"
|
|
|
|
class="dropdown-divider"
|
2022-07-12 08:55:28 +00:00
|
|
|
v-if="resourceProviders?.length"
|
2022-01-10 14:19:16 +00:00
|
|
|
/>
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-dropdown-item
|
2022-01-10 14:19:16 +00:00
|
|
|
aria-role="listitem"
|
|
|
|
v-for="resourceProvider in resourceProviders"
|
|
|
|
:key="resourceProvider.software"
|
|
|
|
@click="createResourceFromProvider(resourceProvider)"
|
2020-02-18 07:57:00 +00:00
|
|
|
>
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-icon :icon="mapServiceTypeToIcon[resourceProvider.software]" />
|
2022-01-10 14:19:16 +00:00
|
|
|
{{ createSentenceForType(resourceProvider.software) }}
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-dropdown-item>
|
|
|
|
</o-dropdown>
|
2022-01-10 14:19:16 +00:00
|
|
|
</li>
|
|
|
|
</breadcrumbs-nav>
|
2022-07-12 08:55:28 +00:00
|
|
|
<DraggableList
|
|
|
|
:resources="resource.children.elements"
|
|
|
|
:isRoot="resource.path === '/'"
|
|
|
|
:group="resource.actor"
|
|
|
|
@delete="(resourceID: string) => deleteResource({
|
|
|
|
id: resourceID,
|
|
|
|
})"
|
|
|
|
@update="updateResource"
|
|
|
|
@rename="handleRename"
|
|
|
|
@move="handleMove"
|
|
|
|
/>
|
|
|
|
<o-pagination
|
2021-06-14 13:12:38 +00:00
|
|
|
v-if="resource.children.total > RESOURCES_PER_PAGE"
|
|
|
|
:total="resource.children.total"
|
|
|
|
v-model="page"
|
|
|
|
:per-page="RESOURCES_PER_PAGE"
|
|
|
|
:aria-next-label="$t('Next page')"
|
|
|
|
:aria-previous-label="$t('Previous page')"
|
|
|
|
:aria-page-label="$t('Page')"
|
|
|
|
:aria-current-label="$t('Current page')"
|
|
|
|
>
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-pagination>
|
|
|
|
<o-modal
|
|
|
|
v-model:active="renameModal"
|
2021-11-13 11:33:14 +00:00
|
|
|
has-modal-card
|
|
|
|
:close-button-aria-label="$t('Close')"
|
|
|
|
>
|
2022-07-12 08:55:28 +00:00
|
|
|
<div class="w-full md:w-[640px]">
|
|
|
|
<section>
|
2020-02-18 07:57:00 +00:00
|
|
|
<form @submit.prevent="renameResource">
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-field :label="$t('Title')">
|
|
|
|
<o-input
|
2022-03-31 08:41:38 +00:00
|
|
|
ref="resourceRenameInput"
|
|
|
|
aria-required="true"
|
|
|
|
v-model="updatedResource.title"
|
|
|
|
/>
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-field>
|
2020-02-18 07:57:00 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-button native-type="submit">{{
|
2020-11-30 09:24:11 +00:00
|
|
|
$t("Rename resource")
|
2022-07-12 08:55:28 +00:00
|
|
|
}}</o-button>
|
2020-02-18 07:57:00 +00:00
|
|
|
</form>
|
|
|
|
</section>
|
|
|
|
</div>
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-modal>
|
|
|
|
<o-modal
|
|
|
|
v-model:active="moveModal"
|
2021-11-13 11:33:14 +00:00
|
|
|
has-modal-card
|
|
|
|
:close-button-aria-label="$t('Close')"
|
|
|
|
>
|
2022-07-12 08:55:28 +00:00
|
|
|
<div class="w-full md:w-[640px]">
|
|
|
|
<section>
|
2020-06-25 16:47:17 +00:00
|
|
|
<resource-selector
|
|
|
|
:initialResource="updatedResource"
|
2020-10-19 17:21:39 +00:00
|
|
|
:username="usernameWithDomain(resource.actor)"
|
2020-11-27 18:27:44 +00:00
|
|
|
@update-resource="moveResource"
|
|
|
|
@close-move-modal="moveModal = false"
|
2020-06-25 16:47:17 +00:00
|
|
|
/>
|
|
|
|
</section>
|
|
|
|
</div>
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-modal>
|
|
|
|
<o-modal
|
|
|
|
v-model:active="createResourceModal"
|
2021-11-13 11:33:14 +00:00
|
|
|
has-modal-card
|
|
|
|
:close-button-aria-label="$t('Close')"
|
2022-03-31 08:41:38 +00:00
|
|
|
trap-focus
|
2021-11-13 11:33:14 +00:00
|
|
|
>
|
2022-07-12 08:55:28 +00:00
|
|
|
<div class="w-full md:w-[640px]">
|
|
|
|
<section>
|
|
|
|
<o-notification variant="danger" v-if="modalError">
|
2022-03-31 08:41:38 +00:00
|
|
|
{{ modalError }}
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-notification>
|
2020-02-18 07:57:00 +00:00
|
|
|
<form @submit.prevent="createResource">
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-field :label="$t('Title')" label-for="new-resource-title">
|
|
|
|
<o-input
|
2022-03-31 08:41:38 +00:00
|
|
|
ref="modalNewResourceInput"
|
2021-09-07 15:52:34 +00:00
|
|
|
aria-required="true"
|
|
|
|
v-model="newResource.title"
|
|
|
|
id="new-resource-title"
|
|
|
|
/>
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-field>
|
2020-02-18 07:57:00 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-button native-type="submit">{{
|
2020-11-30 09:24:11 +00:00
|
|
|
createResourceButtonLabel
|
2022-07-12 08:55:28 +00:00
|
|
|
}}</o-button>
|
2020-02-18 07:57:00 +00:00
|
|
|
</form>
|
|
|
|
</section>
|
|
|
|
</div>
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-modal>
|
|
|
|
<o-modal
|
|
|
|
v-model:active="createLinkResourceModal"
|
2021-06-14 13:13:08 +00:00
|
|
|
has-modal-card
|
2021-09-07 15:52:34 +00:00
|
|
|
aria-modal
|
2021-11-13 11:33:14 +00:00
|
|
|
:close-button-aria-label="$t('Close')"
|
2022-03-31 08:41:38 +00:00
|
|
|
trap-focus
|
2022-07-12 08:55:28 +00:00
|
|
|
:width="640"
|
2021-06-14 13:13:08 +00:00
|
|
|
>
|
2022-07-12 08:55:28 +00:00
|
|
|
<div class="w-full md:w-[640px]">
|
|
|
|
<section class="p-10">
|
|
|
|
<o-notification variant="danger" v-if="modalError">
|
2021-03-24 09:45:29 +00:00
|
|
|
{{ modalError }}
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-notification>
|
2020-02-18 07:57:00 +00:00
|
|
|
<form @submit.prevent="createResource">
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-field expanded :label="$t('URL')" label-for="new-resource-url">
|
|
|
|
<o-input
|
2021-09-07 15:52:34 +00:00
|
|
|
id="new-resource-url"
|
2020-02-18 07:57:00 +00:00
|
|
|
type="url"
|
|
|
|
required
|
|
|
|
v-model="newResource.resourceUrl"
|
|
|
|
@blur="previewResource"
|
2022-03-31 08:41:38 +00:00
|
|
|
ref="modalNewResourceLinkInput"
|
2020-02-18 07:57:00 +00:00
|
|
|
/>
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-field>
|
2020-02-18 07:57:00 +00:00
|
|
|
|
|
|
|
<div class="new-resource-preview" v-if="newResource.title">
|
2021-06-14 13:13:08 +00:00
|
|
|
<resource-item :resource="newResource" :preview="true" />
|
2020-02-18 07:57:00 +00:00
|
|
|
</div>
|
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-field :label="$t('Title')" label-for="new-resource-link-title">
|
|
|
|
<o-input
|
2021-09-07 15:52:34 +00:00
|
|
|
aria-required="true"
|
|
|
|
v-model="newResource.title"
|
|
|
|
id="new-resource-link-title"
|
|
|
|
/>
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-field>
|
2020-02-18 07:57:00 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-field
|
2021-09-07 15:52:34 +00:00
|
|
|
:label="$t('Description')"
|
|
|
|
label-for="new-resource-summary"
|
|
|
|
>
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-input
|
2021-09-07 15:52:34 +00:00
|
|
|
type="textarea"
|
|
|
|
v-model="newResource.summary"
|
|
|
|
id="new-resource-summary"
|
|
|
|
/>
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-field>
|
2020-02-18 07:57:00 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
<o-button native-type="submit" class="mt-2">{{
|
2020-11-30 09:24:11 +00:00
|
|
|
$t("Create resource")
|
2022-07-12 08:55:28 +00:00
|
|
|
}}</o-button>
|
2020-02-18 07:57:00 +00:00
|
|
|
</form>
|
|
|
|
</section>
|
|
|
|
</div>
|
2022-07-12 08:55:28 +00:00
|
|
|
</o-modal>
|
2020-02-18 07:57:00 +00:00
|
|
|
</div>
|
|
|
|
</template>
|
2022-07-12 08:55:28 +00:00
|
|
|
<script lang="ts" setup>
|
2020-02-18 07:57:00 +00:00
|
|
|
import ResourceItem from "@/components/Resource/ResourceItem.vue";
|
2022-07-12 08:55:28 +00:00
|
|
|
import { displayName, usernameWithDomain } from "@/types/actor";
|
|
|
|
import RouteName from "@/router/name";
|
2020-11-30 09:24:11 +00:00
|
|
|
import {
|
|
|
|
IResource,
|
|
|
|
mapServiceTypeToIcon,
|
|
|
|
IProvider,
|
2022-07-12 08:55:28 +00:00
|
|
|
IResourceMetadata,
|
|
|
|
} from "@/types/resource";
|
2020-02-18 07:57:00 +00:00
|
|
|
import {
|
|
|
|
CREATE_RESOURCE,
|
|
|
|
DELETE_RESOURCE,
|
|
|
|
PREVIEW_RESOURCE_LINK,
|
|
|
|
GET_RESOURCE,
|
|
|
|
UPDATE_RESOURCE,
|
2022-07-12 08:55:28 +00:00
|
|
|
} from "@/graphql/resources";
|
|
|
|
import ResourceSelector from "@/components/Resource/ResourceSelector.vue";
|
2021-08-02 16:11:22 +00:00
|
|
|
import {
|
|
|
|
ApolloCache,
|
|
|
|
FetchResult,
|
|
|
|
InternalRefetchQueriesInclude,
|
|
|
|
} from "@apollo/client/core";
|
2022-07-12 08:55:28 +00:00
|
|
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
|
|
|
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
|
|
|
import { computed, nextTick, reactive, ref, watch } from "vue";
|
|
|
|
import { useI18n } from "vue-i18n";
|
|
|
|
import { integerTransformer, useRouteQuery } from "vue-use-route-query";
|
|
|
|
import { useRouter } from "vue-router";
|
|
|
|
import { useHead } from "@vueuse/head";
|
|
|
|
import { useResourceProviders } from "@/composition/apollo/config";
|
|
|
|
import Folder from "vue-material-design-icons/Folder.vue";
|
|
|
|
import Link from "vue-material-design-icons/Link.vue";
|
|
|
|
import DraggableList from "@/components/Resource/DraggableList.vue";
|
|
|
|
import { resourcePathArray } from "@/components/Resource/utils";
|
|
|
|
import { AbsintheGraphQLErrors } from "@/types/errors.model";
|
|
|
|
|
|
|
|
const RESOURCES_PER_PAGE = 10;
|
|
|
|
const page = useRouteQuery("page", 1, integerTransformer);
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
path: string | string[];
|
|
|
|
preferredUsername: string;
|
|
|
|
}>();
|
|
|
|
|
|
|
|
const {
|
|
|
|
result: resourceResult,
|
|
|
|
onError: onGetResourceError,
|
|
|
|
fetchMore,
|
|
|
|
} = useQuery<{
|
|
|
|
resource: IResource;
|
|
|
|
}>(GET_RESOURCE, () => {
|
|
|
|
let path = Array.isArray(props.path) ? props.path.join("/") : props.path;
|
|
|
|
path = path[0] !== "/" ? `/${path}` : path;
|
|
|
|
return {
|
|
|
|
path,
|
|
|
|
username: props.preferredUsername,
|
|
|
|
page: page.value,
|
|
|
|
limit: RESOURCES_PER_PAGE,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const resource = computed(() => resourceResult.value?.resource);
|
|
|
|
|
|
|
|
onGetResourceError(({ graphQLErrors }) => {
|
|
|
|
handleErrors(graphQLErrors);
|
|
|
|
});
|
|
|
|
|
|
|
|
const { currentActor } = useCurrentActorClient();
|
|
|
|
|
|
|
|
const { resourceProviders } = useResourceProviders();
|
|
|
|
|
|
|
|
const { t } = useI18n({ useScope: "global" });
|
|
|
|
|
|
|
|
// config: CONFIG,
|
|
|
|
|
|
|
|
const newResource = reactive<IResource>({
|
|
|
|
title: "",
|
|
|
|
summary: "",
|
|
|
|
resourceUrl: "",
|
|
|
|
children: { elements: [], total: 0 },
|
|
|
|
metadata: {},
|
|
|
|
type: "link",
|
|
|
|
});
|
|
|
|
|
|
|
|
const updatedResource = ref<IResource>({
|
|
|
|
title: "",
|
|
|
|
resourceUrl: "",
|
|
|
|
metadata: {},
|
|
|
|
children: { elements: [], total: 0 },
|
|
|
|
path: undefined,
|
|
|
|
});
|
|
|
|
|
|
|
|
const createResourceModal = ref(false);
|
|
|
|
const createLinkResourceModal = ref(false);
|
|
|
|
const moveModal = ref(false);
|
|
|
|
const renameModal = ref(false);
|
|
|
|
const modalError = ref("");
|
|
|
|
|
|
|
|
const resourceRenameInput = ref<HTMLElement>();
|
|
|
|
const modalNewResourceInput = ref<HTMLElement>();
|
|
|
|
const modalNewResourceLinkInput = ref<HTMLElement>();
|
|
|
|
|
|
|
|
const actualPath = computed((): string => {
|
|
|
|
const path = Array.isArray(props.path) ? props.path.join("/") : props.path;
|
|
|
|
return path[0] !== "/" ? `/${path}` : path;
|
|
|
|
});
|
|
|
|
|
|
|
|
const filteredPath = computed((): string[] => {
|
|
|
|
if (resource.value?.path !== "/") {
|
|
|
|
return resourcePathArray(resource.value);
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
});
|
|
|
|
|
|
|
|
const isRoot = computed((): boolean => {
|
|
|
|
return actualPath.value === "/";
|
|
|
|
});
|
|
|
|
|
|
|
|
const lastFragment = computed((): string | undefined => {
|
|
|
|
return filteredPath.value.slice(-1)[0];
|
|
|
|
});
|
|
|
|
|
|
|
|
const {
|
|
|
|
mutate: createResourceMutation,
|
|
|
|
onDone: createResourceDone,
|
|
|
|
onError: createResourceError,
|
|
|
|
} = useMutation(CREATE_RESOURCE, () => ({
|
|
|
|
refetchQueries: () => postRefreshQueries(),
|
|
|
|
}));
|
|
|
|
|
|
|
|
createResourceDone(({ data }) => {
|
|
|
|
createLinkResourceModal.value = false;
|
|
|
|
createResourceModal.value = false;
|
|
|
|
newResource.title = "";
|
|
|
|
newResource.summary = "";
|
|
|
|
newResource.resourceUrl = "";
|
|
|
|
});
|
|
|
|
|
|
|
|
createResourceError((err) => {
|
|
|
|
console.error(err);
|
|
|
|
modalError.value = err.graphQLErrors[0].message;
|
|
|
|
});
|
|
|
|
|
|
|
|
const createResource = () => {
|
|
|
|
if (!resource.value?.actor) return;
|
|
|
|
modalError.value = "";
|
|
|
|
createResourceMutation({
|
|
|
|
title: newResource.title,
|
|
|
|
summary: newResource.summary,
|
|
|
|
actorId: resource.value.actor?.id,
|
|
|
|
resourceUrl: newResource.resourceUrl,
|
|
|
|
parentId: resource.value?.id?.startsWith("root_")
|
|
|
|
? null
|
|
|
|
: resource.value?.id,
|
|
|
|
type: newResource.type,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const {
|
|
|
|
mutate: previewResourceLinkMutation,
|
|
|
|
onDone: previewDone,
|
|
|
|
onError: previewError,
|
|
|
|
} = useMutation<{ previewResourceLink: IResourceMetadata }>(
|
|
|
|
PREVIEW_RESOURCE_LINK
|
|
|
|
);
|
|
|
|
|
|
|
|
previewDone(({ data }) => {
|
|
|
|
if (!data?.previewResourceLink) return;
|
|
|
|
newResource.title = data?.previewResourceLink.title ?? "";
|
|
|
|
newResource.summary = data?.previewResourceLink?.description;
|
|
|
|
newResource.metadata = data?.previewResourceLink;
|
|
|
|
newResource.type = "link";
|
|
|
|
});
|
|
|
|
|
|
|
|
previewError((err) => {
|
|
|
|
console.error(err);
|
|
|
|
modalError.value = err.graphQLErrors[0].message;
|
|
|
|
});
|
|
|
|
|
|
|
|
const previewResource = async (): Promise<void> => {
|
|
|
|
modalError.value = "";
|
|
|
|
if (newResource.resourceUrl === "") return;
|
|
|
|
previewResourceLinkMutation({
|
|
|
|
resourceUrl: newResource.resourceUrl,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const createSentenceForType = (type: string): string => {
|
|
|
|
switch (type) {
|
|
|
|
case "folder":
|
|
|
|
return t("Create a folder") as string;
|
|
|
|
case "pad":
|
|
|
|
return t("Create a pad") as string;
|
|
|
|
case "calc":
|
|
|
|
return t("Create a calc") as string;
|
|
|
|
case "visio":
|
|
|
|
return t("Create a videoconference") as string;
|
|
|
|
default:
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const createLinkModal = async (): Promise<void> => {
|
|
|
|
createLinkResourceModal.value = true;
|
|
|
|
await nextTick();
|
|
|
|
modalNewResourceLinkInput.value?.focus();
|
|
|
|
};
|
|
|
|
|
|
|
|
const createFolderModal = async (): Promise<void> => {
|
|
|
|
newResource.type = "folder";
|
|
|
|
createResourceModal.value = true;
|
|
|
|
await nextTick();
|
|
|
|
modalNewResourceInput.value?.focus();
|
|
|
|
};
|
|
|
|
|
|
|
|
const createResourceFromProvider = async (
|
|
|
|
provider: IProvider
|
|
|
|
): Promise<void> => {
|
|
|
|
newResource.resourceUrl = generateFullResourceUrl(provider);
|
|
|
|
newResource.type = provider.software;
|
|
|
|
createResourceModal.value = true;
|
|
|
|
await nextTick();
|
|
|
|
modalNewResourceInput.value?.focus();
|
|
|
|
};
|
|
|
|
|
|
|
|
const generateFullResourceUrl = (provider: IProvider): string => {
|
|
|
|
const randomString = [...Array(10)]
|
|
|
|
.map(() => Math.random().toString(36)[3])
|
|
|
|
.join("")
|
|
|
|
.replace(/(.|$)/g, (c) =>
|
|
|
|
c[!Math.round(Math.random()) ? "toString" : "toLowerCase"]()
|
|
|
|
);
|
|
|
|
switch (provider.type) {
|
|
|
|
case "ethercalc":
|
|
|
|
case "etherpad":
|
|
|
|
case "jitsi":
|
|
|
|
default:
|
|
|
|
return `${provider.endpoint}${randomString}`;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const createResourceButtonLabel = computed((): string => {
|
|
|
|
if (!newResource.type) return "";
|
|
|
|
return createSentenceForType(newResource.type);
|
|
|
|
});
|
|
|
|
|
|
|
|
// eslint-disable-next-line class-methods-use-this
|
|
|
|
const postRefreshQueries = (): InternalRefetchQueriesInclude => {
|
|
|
|
return [
|
|
|
|
{
|
2020-02-18 07:57:00 +00:00
|
|
|
query: GET_RESOURCE,
|
2022-07-12 08:55:28 +00:00
|
|
|
variables: {
|
|
|
|
path: actualPath.value,
|
|
|
|
username: props.preferredUsername,
|
|
|
|
page: page.value,
|
|
|
|
limit: RESOURCES_PER_PAGE,
|
2020-10-19 17:21:39 +00:00
|
|
|
},
|
2020-02-18 07:57:00 +00:00
|
|
|
},
|
2022-07-12 08:55:28 +00:00
|
|
|
];
|
|
|
|
};
|
2022-03-31 08:41:38 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
const { mutate: deleteResource, onError: onDeleteResourceError } = useMutation(
|
|
|
|
DELETE_RESOURCE,
|
|
|
|
() => ({
|
|
|
|
refetchQueries: () => postRefreshQueries(),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
onDeleteResourceError((e) => console.error(e));
|
|
|
|
|
|
|
|
const handleRename = async (resource: IResource): Promise<void> => {
|
|
|
|
renameModal.value = true;
|
|
|
|
updatedResource.value = { ...resource };
|
|
|
|
await nextTick();
|
|
|
|
resourceRenameInput.value?.$el.focus();
|
|
|
|
resourceRenameInput.value?.$el.querySelector("input")?.select();
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleMove = (resource: IResource): void => {
|
|
|
|
moveModal.value = true;
|
|
|
|
updatedResource.value = { ...resource };
|
|
|
|
};
|
|
|
|
|
|
|
|
const moveResource = async (
|
|
|
|
resource: IResource,
|
|
|
|
oldParent: IResource | undefined
|
|
|
|
): Promise<void> => {
|
|
|
|
const parentPath = oldParent && oldParent.path ? oldParent.path || "/" : "/";
|
|
|
|
await updateResource(resource, parentPath);
|
|
|
|
moveModal.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const renameResource = async (): Promise<void> => {
|
|
|
|
await updateResource(updatedResource.value);
|
|
|
|
renameModal.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const { mutate: updateResourceMutation } = useMutation<{
|
|
|
|
updateResource: IResource;
|
|
|
|
}>(UPDATE_RESOURCE, () => ({
|
|
|
|
refetchQueries: () => postRefreshQueries(),
|
|
|
|
update: (
|
|
|
|
store: ApolloCache<{ updateResource: IResource }>,
|
|
|
|
{ data }: FetchResult,
|
|
|
|
{ context }
|
|
|
|
) => {
|
|
|
|
const parentPath = context?.parentPath;
|
|
|
|
if (!data || data.updateResource == null || parentPath == null) return;
|
|
|
|
if (!resource.value?.actor) return;
|
|
|
|
|
|
|
|
console.log("Removing ressource from old parent");
|
|
|
|
const oldParentCachedData = store.readQuery<{ resource: IResource }>({
|
|
|
|
query: GET_RESOURCE,
|
|
|
|
variables: {
|
|
|
|
path: parentPath,
|
|
|
|
username: resource.value.actor.preferredUsername,
|
|
|
|
},
|
2021-06-14 13:12:38 +00:00
|
|
|
});
|
2022-07-12 08:55:28 +00:00
|
|
|
if (oldParentCachedData == null) return;
|
|
|
|
const { resource: oldParentCachedResource } = oldParentCachedData;
|
|
|
|
if (oldParentCachedResource == null) {
|
|
|
|
console.error("Cannot update resource cache, because of null value.");
|
|
|
|
return;
|
2020-02-18 07:57:00 +00:00
|
|
|
}
|
2022-07-12 08:55:28 +00:00
|
|
|
const updatedResource: IResource = data.updateResource;
|
2020-02-18 07:57:00 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
const updatedElementList = oldParentCachedResource.children.elements.filter(
|
|
|
|
(cachedResource) => cachedResource.id !== updatedResource.id
|
|
|
|
);
|
2020-02-18 07:57:00 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
store.writeQuery({
|
|
|
|
query: GET_RESOURCE,
|
|
|
|
variables: {
|
|
|
|
path: parentPath,
|
|
|
|
username: resource.value.actor.preferredUsername,
|
|
|
|
},
|
|
|
|
data: {
|
|
|
|
resource: {
|
|
|
|
...oldParentCachedResource,
|
|
|
|
children: {
|
|
|
|
...oldParentCachedResource.children,
|
|
|
|
elements: [...updatedElementList],
|
|
|
|
},
|
2020-10-19 17:21:39 +00:00
|
|
|
},
|
|
|
|
},
|
2022-07-12 08:55:28 +00:00
|
|
|
});
|
|
|
|
console.log("Finished removing ressource from old parent");
|
2020-10-19 17:21:39 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
console.log("Adding resource to new parent");
|
|
|
|
if (!updatedResource.parent || !updatedResource.parent.path) {
|
|
|
|
console.log("No cache found for new parent");
|
|
|
|
return;
|
2020-02-18 07:57:00 +00:00
|
|
|
}
|
2022-07-12 08:55:28 +00:00
|
|
|
const newParentCachedData = store.readQuery<{ resource: IResource }>({
|
|
|
|
query: GET_RESOURCE,
|
|
|
|
variables: {
|
|
|
|
path: updatedResource.parent.path,
|
|
|
|
username: resource.value.actor.preferredUsername,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
if (newParentCachedData == null) return;
|
|
|
|
const { resource: newParentCachedResource } = newParentCachedData;
|
|
|
|
if (newParentCachedResource == null) {
|
|
|
|
console.error("Cannot update resource cache, because of null value.");
|
|
|
|
return;
|
2020-02-18 07:57:00 +00:00
|
|
|
}
|
2020-10-19 17:21:39 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
store.writeQuery({
|
|
|
|
query: GET_RESOURCE,
|
2021-06-14 13:12:38 +00:00
|
|
|
variables: {
|
2022-07-12 08:55:28 +00:00
|
|
|
path: updatedResource.parent.path,
|
|
|
|
username: resource.value.actor.preferredUsername,
|
|
|
|
},
|
|
|
|
data: {
|
|
|
|
resource: {
|
|
|
|
...newParentCachedResource,
|
|
|
|
children: {
|
|
|
|
...newParentCachedResource.children,
|
|
|
|
elements: [...newParentCachedResource.children.elements, resource],
|
|
|
|
},
|
|
|
|
},
|
2021-06-14 13:12:38 +00:00
|
|
|
},
|
|
|
|
});
|
2022-07-12 08:55:28 +00:00
|
|
|
console.log("Finished adding resource to new parent");
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
|
|
|
const updateResource = async (
|
|
|
|
resource: IResource,
|
|
|
|
parentPath: string | null = null
|
|
|
|
): Promise<void> => {
|
|
|
|
updateResourceMutation(
|
|
|
|
{
|
|
|
|
id: resource.id,
|
|
|
|
title: resource.title,
|
|
|
|
parentId: resource.parent ? resource.parent.id : null,
|
|
|
|
path: resource.path,
|
|
|
|
},
|
|
|
|
{ context: { parentPath } }
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
watch(page, () => {
|
|
|
|
fetchMore({
|
|
|
|
// New variables
|
|
|
|
variables: {
|
|
|
|
page: page.value,
|
|
|
|
limit: RESOURCES_PER_PAGE,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
2021-06-14 13:12:38 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
const router = useRouter();
|
2021-06-14 13:12:38 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
const handleErrors = (errors: AbsintheGraphQLErrors): void => {
|
|
|
|
if (errors.some((error) => error.status_code === 404)) {
|
|
|
|
router.replace({ name: RouteName.PAGE_NOT_FOUND });
|
2021-06-14 13:12:38 +00:00
|
|
|
}
|
2022-07-12 08:55:28 +00:00
|
|
|
};
|
2022-01-10 14:19:16 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
const breadcrumbLinks = computed(() => {
|
|
|
|
if (!resource.value?.actor) return [];
|
|
|
|
const resourceActor = resource.value.actor;
|
|
|
|
const links = [
|
|
|
|
{
|
|
|
|
name: RouteName.GROUP,
|
|
|
|
params: { preferredUsername: usernameWithDomain(resource.value.actor) },
|
|
|
|
text: displayName(resource.value.actor),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: RouteName.RESOURCE_FOLDER_ROOT,
|
|
|
|
params: { preferredUsername: usernameWithDomain(resource.value.actor) },
|
|
|
|
text: t("Resources") as string,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
links.push(
|
|
|
|
...filteredPath.value.map((pathFragment, index) => {
|
|
|
|
return {
|
|
|
|
name: RouteName.RESOURCE_FOLDER,
|
|
|
|
params: {
|
|
|
|
path: resourcePathArray(resource.value).slice(
|
|
|
|
0,
|
|
|
|
index + 1
|
|
|
|
) as unknown as string,
|
|
|
|
preferredUsername: usernameWithDomain(resourceActor),
|
|
|
|
},
|
|
|
|
text: pathFragment,
|
|
|
|
};
|
|
|
|
})
|
|
|
|
);
|
|
|
|
return links;
|
|
|
|
});
|
|
|
|
|
|
|
|
useHead({
|
|
|
|
title: computed(() =>
|
|
|
|
isRoot.value
|
|
|
|
? t("Resources")
|
|
|
|
: t("{folder} - Resources", {
|
|
|
|
folder: lastFragment,
|
|
|
|
})
|
|
|
|
),
|
|
|
|
});
|
2020-02-18 07:57:00 +00:00
|
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
2021-11-04 17:14:36 +00:00
|
|
|
@use "@/styles/_mixins" as *;
|
|
|
|
|
2020-02-18 07:57:00 +00:00
|
|
|
nav.breadcrumb ul {
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
li:last-child .dropdown {
|
2021-11-04 17:14:36 +00:00
|
|
|
@include margin-left(5px);
|
2020-02-18 07:57:00 +00:00
|
|
|
|
|
|
|
a {
|
|
|
|
justify-content: left;
|
|
|
|
color: inherit;
|
|
|
|
padding: 0.375rem 1rem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.list-header {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
|
|
.list-header-right {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
:deep(.b-checkbox.checkbox) {
|
2021-11-04 17:14:36 +00:00
|
|
|
@include margin-left(10px);
|
2021-06-14 13:13:08 +00:00
|
|
|
}
|
|
|
|
|
2020-02-18 07:57:00 +00:00
|
|
|
.actions {
|
2021-11-04 17:14:36 +00:00
|
|
|
@include margin-right(5px);
|
2020-02-18 07:57:00 +00:00
|
|
|
|
|
|
|
& > * {
|
2021-11-04 17:14:36 +00:00
|
|
|
@include margin-left(5px);
|
2020-02-18 07:57:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.resource-item,
|
|
|
|
.new-resource-preview {
|
|
|
|
display: flex;
|
|
|
|
font-size: 14px;
|
|
|
|
border: 1px solid #c0cdd9;
|
|
|
|
border-radius: 4px;
|
2022-07-12 08:55:28 +00:00
|
|
|
// color: #444b5d;
|
2020-02-18 07:57:00 +00:00
|
|
|
margin-top: 14px;
|
2021-06-14 13:13:08 +00:00
|
|
|
margin-bottom: 14px;
|
2020-02-18 07:57:00 +00:00
|
|
|
|
|
|
|
.resource-checkbox {
|
|
|
|
align-self: center;
|
2021-11-04 17:14:36 +00:00
|
|
|
@include padding-left(10px);
|
2020-02-18 07:57:00 +00:00
|
|
|
opacity: 0.3;
|
2021-06-14 13:13:08 +00:00
|
|
|
|
2022-07-12 08:55:28 +00:00
|
|
|
:deep(.b-checkbox.checkbox) {
|
2021-11-04 17:14:36 +00:00
|
|
|
@include margin-right(0.25rem);
|
2021-06-14 13:13:08 +00:00
|
|
|
}
|
2020-02-18 07:57:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
&:hover .resource-checkbox,
|
|
|
|
.resource-checkbox.checked {
|
|
|
|
opacity: 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|