2020-02-18 08:57:00 +01:00
|
|
|
<template>
|
|
|
|
<div class="container section" v-if="resource">
|
2022-01-10 15:19:16 +01:00
|
|
|
<breadcrumbs-nav :links="breadcrumbLinks">
|
|
|
|
<li>
|
|
|
|
<b-dropdown aria-role="list">
|
|
|
|
<b-button class="button is-primary" slot="trigger">+</b-button>
|
|
|
|
|
|
|
|
<b-dropdown-item aria-role="listitem" @click="createFolderModal">
|
|
|
|
<b-icon icon="folder" />
|
|
|
|
{{ $t("New folder") }}
|
|
|
|
</b-dropdown-item>
|
2022-03-31 10:41:38 +02:00
|
|
|
<b-dropdown-item aria-role="listitem" @click="createLinkModal">
|
2022-01-10 15:19:16 +01:00
|
|
|
<b-icon icon="link" />
|
|
|
|
{{ $t("New link") }}
|
|
|
|
</b-dropdown-item>
|
|
|
|
<hr
|
|
|
|
role="presentation"
|
|
|
|
class="dropdown-divider"
|
|
|
|
v-if="resourceProviders.length"
|
|
|
|
/>
|
|
|
|
<b-dropdown-item
|
|
|
|
aria-role="listitem"
|
|
|
|
v-for="resourceProvider in resourceProviders"
|
|
|
|
:key="resourceProvider.software"
|
|
|
|
@click="createResourceFromProvider(resourceProvider)"
|
2020-02-18 08:57:00 +01:00
|
|
|
>
|
2022-01-10 15:19:16 +01:00
|
|
|
<b-icon :icon="mapServiceTypeToIcon[resourceProvider.software]" />
|
|
|
|
{{ createSentenceForType(resourceProvider.software) }}
|
|
|
|
</b-dropdown-item>
|
|
|
|
</b-dropdown>
|
|
|
|
</li>
|
|
|
|
</breadcrumbs-nav>
|
2020-02-18 08:57:00 +01:00
|
|
|
<section>
|
2020-08-31 12:40:30 +02:00
|
|
|
<p v-if="resource.path === '/'" class="module-description">
|
2020-11-30 10:24:11 +01:00
|
|
|
{{
|
|
|
|
$t("A place to store links to documents or resources of any type.")
|
|
|
|
}}
|
2020-08-31 12:40:30 +02:00
|
|
|
</p>
|
2020-02-18 08:57:00 +01:00
|
|
|
<div class="list-header">
|
|
|
|
<div class="list-header-right">
|
2020-06-25 18:47:17 +02:00
|
|
|
<b-checkbox v-model="checkedAll" v-if="resource.children.total > 0" />
|
2020-02-18 08:57:00 +01:00
|
|
|
<div class="actions" v-if="validCheckedResources.length > 0">
|
|
|
|
<small>
|
|
|
|
{{
|
|
|
|
$tc("No resources selected", validCheckedResources.length, {
|
|
|
|
count: validCheckedResources.length,
|
|
|
|
})
|
|
|
|
}}
|
|
|
|
</small>
|
|
|
|
<b-button
|
|
|
|
type="is-danger"
|
|
|
|
icon-right="delete"
|
|
|
|
size="is-small"
|
|
|
|
@click="deleteMultipleResources"
|
|
|
|
>{{ $t("Delete") }}</b-button
|
|
|
|
>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-06-25 18:47:17 +02:00
|
|
|
<draggable
|
|
|
|
v-model="resource.children.elements"
|
|
|
|
:sort="false"
|
|
|
|
:group="groupObject"
|
|
|
|
v-if="resource.children.total > 0"
|
|
|
|
>
|
2020-02-18 08:57:00 +01:00
|
|
|
<transition-group>
|
2020-11-30 10:24:11 +01:00
|
|
|
<div
|
|
|
|
v-for="localResource in resource.children.elements"
|
|
|
|
:key="localResource.id"
|
|
|
|
>
|
2020-02-18 08:57:00 +01:00
|
|
|
<div class="resource-item">
|
|
|
|
<div
|
|
|
|
class="resource-checkbox"
|
|
|
|
:class="{ checked: checkedResources[localResource.id] }"
|
|
|
|
>
|
|
|
|
<b-checkbox v-model="checkedResources[localResource.id]" />
|
|
|
|
</div>
|
|
|
|
<resource-item
|
|
|
|
:resource="localResource"
|
|
|
|
v-if="localResource.type !== 'folder'"
|
|
|
|
@delete="deleteResource"
|
|
|
|
@rename="handleRename"
|
2020-06-25 18:47:17 +02:00
|
|
|
@move="handleMove"
|
2020-02-18 08:57:00 +01:00
|
|
|
/>
|
|
|
|
<folder-item
|
|
|
|
:resource="localResource"
|
|
|
|
:group="resource.actor"
|
|
|
|
@delete="deleteResource"
|
2020-10-19 19:21:39 +02:00
|
|
|
@rename="handleRename"
|
2020-06-25 18:47:17 +02:00
|
|
|
@move="handleMove"
|
2020-02-18 08:57:00 +01:00
|
|
|
v-else
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</transition-group>
|
|
|
|
</draggable>
|
2020-11-30 10:24:11 +01:00
|
|
|
<div
|
|
|
|
class="content has-text-centered has-text-grey"
|
|
|
|
v-if="resource.children.total === 0"
|
|
|
|
>
|
2020-06-25 18:47:17 +02:00
|
|
|
<p>{{ $t("No resources in this folder") }}</p>
|
|
|
|
</div>
|
2020-02-18 08:57:00 +01:00
|
|
|
</section>
|
2021-06-14 15:12:38 +02:00
|
|
|
<b-pagination
|
|
|
|
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')"
|
|
|
|
>
|
|
|
|
</b-pagination>
|
2021-11-13 12:33:14 +01:00
|
|
|
<b-modal
|
|
|
|
:active.sync="renameModal"
|
|
|
|
has-modal-card
|
|
|
|
:close-button-aria-label="$t('Close')"
|
|
|
|
>
|
2020-02-18 08:57:00 +01:00
|
|
|
<div class="modal-card">
|
|
|
|
<section class="modal-card-body">
|
|
|
|
<form @submit.prevent="renameResource">
|
|
|
|
<b-field :label="$t('Title')">
|
2022-03-31 10:41:38 +02:00
|
|
|
<b-input
|
|
|
|
ref="resourceRenameInput"
|
|
|
|
aria-required="true"
|
|
|
|
v-model="updatedResource.title"
|
|
|
|
/>
|
2020-02-18 08:57:00 +01:00
|
|
|
</b-field>
|
|
|
|
|
2020-11-30 10:24:11 +01:00
|
|
|
<b-button native-type="submit">{{
|
|
|
|
$t("Rename resource")
|
|
|
|
}}</b-button>
|
2020-02-18 08:57:00 +01:00
|
|
|
</form>
|
|
|
|
</section>
|
|
|
|
</div>
|
|
|
|
</b-modal>
|
2021-11-13 12:33:14 +01:00
|
|
|
<b-modal
|
|
|
|
:active.sync="moveModal"
|
|
|
|
has-modal-card
|
|
|
|
:close-button-aria-label="$t('Close')"
|
|
|
|
>
|
2020-06-25 18:47:17 +02:00
|
|
|
<div class="modal-card">
|
|
|
|
<section class="modal-card-body">
|
|
|
|
<resource-selector
|
|
|
|
:initialResource="updatedResource"
|
2020-10-19 19:21:39 +02:00
|
|
|
:username="usernameWithDomain(resource.actor)"
|
2020-11-27 19:27:44 +01:00
|
|
|
@update-resource="moveResource"
|
|
|
|
@close-move-modal="moveModal = false"
|
2020-06-25 18:47:17 +02:00
|
|
|
/>
|
|
|
|
</section>
|
|
|
|
</div>
|
|
|
|
</b-modal>
|
2021-11-13 12:33:14 +01:00
|
|
|
<b-modal
|
|
|
|
:active.sync="createResourceModal"
|
|
|
|
has-modal-card
|
|
|
|
:close-button-aria-label="$t('Close')"
|
2022-03-31 10:41:38 +02:00
|
|
|
trap-focus
|
2021-11-13 12:33:14 +01:00
|
|
|
>
|
2020-02-18 08:57:00 +01:00
|
|
|
<div class="modal-card">
|
|
|
|
<section class="modal-card-body">
|
2022-03-31 10:41:38 +02:00
|
|
|
<b-message type="is-danger" v-if="modalError">
|
|
|
|
{{ modalError }}
|
|
|
|
</b-message>
|
2020-02-18 08:57:00 +01:00
|
|
|
<form @submit.prevent="createResource">
|
2021-09-07 17:52:34 +02:00
|
|
|
<b-field :label="$t('Title')" label-for="new-resource-title">
|
|
|
|
<b-input
|
2022-03-31 10:41:38 +02:00
|
|
|
ref="modalNewResourceInput"
|
2021-09-07 17:52:34 +02:00
|
|
|
aria-required="true"
|
|
|
|
v-model="newResource.title"
|
|
|
|
id="new-resource-title"
|
|
|
|
/>
|
2020-02-18 08:57:00 +01:00
|
|
|
</b-field>
|
|
|
|
|
2020-11-30 10:24:11 +01:00
|
|
|
<b-button native-type="submit">{{
|
|
|
|
createResourceButtonLabel
|
|
|
|
}}</b-button>
|
2020-02-18 08:57:00 +01:00
|
|
|
</form>
|
|
|
|
</section>
|
|
|
|
</div>
|
|
|
|
</b-modal>
|
2021-06-14 15:13:08 +02:00
|
|
|
<b-modal
|
|
|
|
:active.sync="createLinkResourceModal"
|
|
|
|
has-modal-card
|
|
|
|
class="link-resource-modal"
|
2021-09-07 17:52:34 +02:00
|
|
|
aria-modal
|
2021-11-13 12:33:14 +01:00
|
|
|
:close-button-aria-label="$t('Close')"
|
2022-03-31 10:41:38 +02:00
|
|
|
trap-focus
|
2021-06-14 15:13:08 +02:00
|
|
|
>
|
2020-02-18 08:57:00 +01:00
|
|
|
<div class="modal-card">
|
|
|
|
<section class="modal-card-body">
|
2021-03-24 10:45:29 +01:00
|
|
|
<b-message type="is-danger" v-if="modalError">
|
|
|
|
{{ modalError }}
|
|
|
|
</b-message>
|
2020-02-18 08:57:00 +01:00
|
|
|
<form @submit.prevent="createResource">
|
2021-09-07 17:52:34 +02:00
|
|
|
<b-field :label="$t('URL')" label-for="new-resource-url">
|
2020-02-18 08:57:00 +01:00
|
|
|
<b-input
|
2021-09-07 17:52:34 +02:00
|
|
|
id="new-resource-url"
|
2020-02-18 08:57:00 +01:00
|
|
|
type="url"
|
|
|
|
required
|
|
|
|
v-model="newResource.resourceUrl"
|
|
|
|
@blur="previewResource"
|
2022-03-31 10:41:38 +02:00
|
|
|
ref="modalNewResourceLinkInput"
|
2020-02-18 08:57:00 +01:00
|
|
|
/>
|
|
|
|
</b-field>
|
|
|
|
|
|
|
|
<div class="new-resource-preview" v-if="newResource.title">
|
2021-06-14 15:13:08 +02:00
|
|
|
<resource-item :resource="newResource" :preview="true" />
|
2020-02-18 08:57:00 +01:00
|
|
|
</div>
|
|
|
|
|
2021-09-07 17:52:34 +02:00
|
|
|
<b-field :label="$t('Title')" label-for="new-resource-link-title">
|
|
|
|
<b-input
|
|
|
|
aria-required="true"
|
|
|
|
v-model="newResource.title"
|
|
|
|
id="new-resource-link-title"
|
|
|
|
/>
|
2020-02-18 08:57:00 +01:00
|
|
|
</b-field>
|
|
|
|
|
2021-09-07 17:52:34 +02:00
|
|
|
<b-field
|
|
|
|
:label="$t('Description')"
|
|
|
|
label-for="new-resource-summary"
|
|
|
|
>
|
|
|
|
<b-input
|
|
|
|
type="textarea"
|
|
|
|
v-model="newResource.summary"
|
|
|
|
id="new-resource-summary"
|
|
|
|
/>
|
2020-02-18 08:57:00 +01:00
|
|
|
</b-field>
|
|
|
|
|
2020-11-30 10:24:11 +01:00
|
|
|
<b-button native-type="submit">{{
|
|
|
|
$t("Create resource")
|
|
|
|
}}</b-button>
|
2020-02-18 08:57:00 +01:00
|
|
|
</form>
|
|
|
|
</section>
|
|
|
|
</div>
|
|
|
|
</b-modal>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
|
|
import { Component, Mixins, Prop, Watch } from "vue-property-decorator";
|
|
|
|
import ResourceItem from "@/components/Resource/ResourceItem.vue";
|
|
|
|
import FolderItem from "@/components/Resource/FolderItem.vue";
|
|
|
|
import Draggable from "vuedraggable";
|
|
|
|
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
2022-01-10 15:19:16 +01:00
|
|
|
import { displayName, IActor, usernameWithDomain } from "../../types/actor";
|
2020-02-18 08:57:00 +01:00
|
|
|
import RouteName from "../../router/name";
|
2020-11-30 10:24:11 +01:00
|
|
|
import {
|
|
|
|
IResource,
|
|
|
|
mapServiceTypeToIcon,
|
|
|
|
IProvider,
|
|
|
|
} from "../../types/resource";
|
2020-02-18 08:57:00 +01:00
|
|
|
import {
|
|
|
|
CREATE_RESOURCE,
|
|
|
|
DELETE_RESOURCE,
|
|
|
|
PREVIEW_RESOURCE_LINK,
|
|
|
|
GET_RESOURCE,
|
|
|
|
UPDATE_RESOURCE,
|
|
|
|
} from "../../graphql/resources";
|
|
|
|
import { CONFIG } from "../../graphql/config";
|
|
|
|
import { IConfig } from "../../types/config.model";
|
|
|
|
import ResourceMixin from "../../mixins/resource";
|
2020-06-25 18:47:17 +02:00
|
|
|
import ResourceSelector from "../../components/Resource/ResourceSelector.vue";
|
2021-08-02 18:11:22 +02:00
|
|
|
import {
|
|
|
|
ApolloCache,
|
|
|
|
FetchResult,
|
|
|
|
InternalRefetchQueriesInclude,
|
|
|
|
} from "@apollo/client/core";
|
2021-06-14 15:12:38 +02:00
|
|
|
import VueRouter from "vue-router";
|
|
|
|
const { isNavigationFailure, NavigationFailureType } = VueRouter;
|
2020-02-18 08:57:00 +01:00
|
|
|
|
|
|
|
@Component({
|
2020-06-25 18:47:17 +02:00
|
|
|
components: { FolderItem, ResourceItem, Draggable, ResourceSelector },
|
2020-02-18 08:57:00 +01:00
|
|
|
apollo: {
|
|
|
|
resource: {
|
|
|
|
query: GET_RESOURCE,
|
2020-08-27 11:53:24 +02:00
|
|
|
fetchPolicy: "cache-and-network",
|
2020-02-18 08:57:00 +01:00
|
|
|
variables() {
|
|
|
|
let path = Array.isArray(this.$route.params.path)
|
|
|
|
? this.$route.params.path.join("/")
|
|
|
|
: this.$route.params.path || this.path;
|
|
|
|
path = path[0] !== "/" ? `/${path}` : path;
|
|
|
|
return {
|
|
|
|
path,
|
|
|
|
username: this.$route.params.preferredUsername,
|
2021-06-14 15:12:38 +02:00
|
|
|
page: this.page,
|
|
|
|
limit: this.RESOURCES_PER_PAGE,
|
2020-02-18 08:57:00 +01:00
|
|
|
};
|
|
|
|
},
|
2020-10-19 19:21:39 +02:00
|
|
|
error({ graphQLErrors }) {
|
|
|
|
this.handleErrors(graphQLErrors);
|
|
|
|
},
|
2020-02-18 08:57:00 +01:00
|
|
|
},
|
|
|
|
config: CONFIG,
|
|
|
|
currentActor: CURRENT_ACTOR_CLIENT,
|
|
|
|
},
|
2021-05-25 16:21:29 +02:00
|
|
|
metaInfo() {
|
|
|
|
return {
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
// @ts-ignore
|
|
|
|
title: this.isRoot
|
|
|
|
? (this.$t("Resources") as string)
|
|
|
|
: (this.$t("{folder} - Resources", {
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
// @ts-ignore
|
|
|
|
folder: this.lastFragment,
|
|
|
|
}) as string),
|
|
|
|
};
|
|
|
|
},
|
2020-02-18 08:57:00 +01:00
|
|
|
})
|
|
|
|
export default class Resources extends Mixins(ResourceMixin) {
|
|
|
|
@Prop({ required: true }) path!: string;
|
|
|
|
|
|
|
|
resource!: IResource;
|
|
|
|
|
|
|
|
config!: IConfig;
|
|
|
|
|
|
|
|
currentActor!: IActor;
|
|
|
|
|
|
|
|
RouteName = RouteName;
|
|
|
|
|
|
|
|
ResourceMixin = ResourceMixin;
|
|
|
|
|
|
|
|
usernameWithDomain = usernameWithDomain;
|
|
|
|
|
2021-06-14 15:12:38 +02:00
|
|
|
RESOURCES_PER_PAGE = 10;
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
newResource: IResource = {
|
|
|
|
title: "",
|
|
|
|
summary: "",
|
|
|
|
resourceUrl: "",
|
|
|
|
children: { elements: [], total: 0 },
|
|
|
|
metadata: {},
|
|
|
|
type: "link",
|
|
|
|
};
|
|
|
|
|
|
|
|
updatedResource: IResource = {
|
|
|
|
title: "",
|
|
|
|
resourceUrl: "",
|
|
|
|
metadata: {},
|
|
|
|
children: { elements: [], total: 0 },
|
|
|
|
path: undefined,
|
|
|
|
};
|
|
|
|
|
|
|
|
checkedResources: { [key: string]: boolean } = {};
|
|
|
|
|
|
|
|
validCheckedResources: string[] = [];
|
|
|
|
|
|
|
|
checkedAll = false;
|
|
|
|
|
|
|
|
createResourceModal = false;
|
|
|
|
|
|
|
|
createLinkResourceModal = false;
|
|
|
|
|
2020-06-25 18:47:17 +02:00
|
|
|
moveModal = false;
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
renameModal = false;
|
|
|
|
|
2021-03-24 10:45:29 +01:00
|
|
|
modalError = "";
|
|
|
|
|
2020-08-31 12:40:30 +02:00
|
|
|
groupObject: Record<string, unknown> = {
|
2020-02-18 08:57:00 +01:00
|
|
|
name: "resources",
|
|
|
|
pull: "clone",
|
|
|
|
put: true,
|
|
|
|
};
|
|
|
|
|
2022-03-31 10:41:38 +02:00
|
|
|
$refs!: {
|
|
|
|
resourceRenameInput: any;
|
|
|
|
modalNewResourceInput: HTMLElement;
|
|
|
|
modalNewResourceLinkInput: HTMLElement;
|
|
|
|
};
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
mapServiceTypeToIcon = mapServiceTypeToIcon;
|
|
|
|
|
2021-06-14 15:12:38 +02:00
|
|
|
get page(): number {
|
|
|
|
return parseInt((this.$route.query.page as string) || "1", 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
set page(page: number) {
|
|
|
|
this.pushRouter({
|
|
|
|
page: page.toString(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-10-19 19:21:39 +02:00
|
|
|
get actualPath(): string {
|
|
|
|
const path = Array.isArray(this.$route.params.path)
|
|
|
|
? this.$route.params.path.join("/")
|
|
|
|
: this.$route.params.path || this.path;
|
|
|
|
return path[0] !== "/" ? `/${path}` : path;
|
|
|
|
}
|
|
|
|
|
2020-11-27 19:27:44 +01:00
|
|
|
get filteredPath(): string[] {
|
|
|
|
if (this.resource && this.resource.path !== "/") {
|
|
|
|
return ResourceMixin.resourcePathArray(this.resource);
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2021-05-25 16:21:29 +02:00
|
|
|
get isRoot(): boolean {
|
|
|
|
return this.actualPath === "/";
|
|
|
|
}
|
|
|
|
|
|
|
|
get lastFragment(): string | undefined {
|
|
|
|
return this.filteredPath.slice(-1)[0];
|
|
|
|
}
|
|
|
|
|
2021-11-02 19:47:54 +01:00
|
|
|
get resourceProviders(): IProvider[] {
|
|
|
|
return this.config?.resourceProviders || [];
|
|
|
|
}
|
|
|
|
|
2020-08-31 12:40:30 +02:00
|
|
|
async createResource(): Promise<void> {
|
2020-02-18 08:57:00 +01:00
|
|
|
if (!this.resource.actor) return;
|
2021-03-24 10:45:29 +01:00
|
|
|
this.modalError = "";
|
2020-02-18 08:57:00 +01:00
|
|
|
try {
|
2020-08-31 12:40:30 +02:00
|
|
|
await this.$apollo.mutate({
|
2020-02-18 08:57:00 +01:00
|
|
|
mutation: CREATE_RESOURCE,
|
|
|
|
variables: {
|
|
|
|
title: this.newResource.title,
|
|
|
|
summary: this.newResource.summary,
|
|
|
|
actorId: this.resource.actor.id,
|
|
|
|
resourceUrl: this.newResource.resourceUrl,
|
|
|
|
parentId:
|
2020-11-30 10:24:11 +01:00
|
|
|
this.resource.id && this.resource.id.startsWith("root_")
|
|
|
|
? null
|
|
|
|
: this.resource.id,
|
2020-02-18 08:57:00 +01:00
|
|
|
type: this.newResource.type,
|
|
|
|
},
|
2020-10-19 19:21:39 +02:00
|
|
|
refetchQueries: () => this.postRefreshQueries(),
|
2020-02-18 08:57:00 +01:00
|
|
|
});
|
|
|
|
this.createLinkResourceModal = false;
|
|
|
|
this.createResourceModal = false;
|
|
|
|
this.newResource.title = "";
|
|
|
|
this.newResource.summary = "";
|
|
|
|
this.newResource.resourceUrl = "";
|
2021-09-29 18:20:33 +02:00
|
|
|
} catch (err: any) {
|
2020-02-18 08:57:00 +01:00
|
|
|
console.error(err);
|
2021-03-24 10:45:29 +01:00
|
|
|
this.modalError = err.graphQLErrors[0].message;
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 12:40:30 +02:00
|
|
|
async previewResource(): Promise<void> {
|
2021-03-24 10:45:29 +01:00
|
|
|
this.modalError = "";
|
|
|
|
try {
|
|
|
|
if (this.newResource.resourceUrl === "") return;
|
|
|
|
const { data } = await this.$apollo.mutate({
|
|
|
|
mutation: PREVIEW_RESOURCE_LINK,
|
|
|
|
variables: {
|
|
|
|
resourceUrl: this.newResource.resourceUrl,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
this.newResource.title = data.previewResourceLink.title;
|
|
|
|
this.newResource.summary = data.previewResourceLink.description;
|
|
|
|
this.newResource.metadata = data.previewResourceLink;
|
|
|
|
this.newResource.type = "link";
|
2021-09-29 18:20:33 +02:00
|
|
|
} catch (err: any) {
|
2021-03-24 10:45:29 +01:00
|
|
|
console.error(err);
|
|
|
|
this.modalError = err.graphQLErrors[0].message;
|
|
|
|
}
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
|
2020-08-31 12:40:30 +02:00
|
|
|
createSentenceForType(type: string): string {
|
2020-02-18 08:57:00 +01:00
|
|
|
switch (type) {
|
2020-06-25 18:47:17 +02:00
|
|
|
case "folder":
|
2020-08-31 12:40:30 +02:00
|
|
|
return this.$t("Create a folder") as string;
|
2020-02-18 08:57:00 +01:00
|
|
|
case "pad":
|
2020-08-31 12:40:30 +02:00
|
|
|
return this.$t("Create a pad") as string;
|
2020-02-18 08:57:00 +01:00
|
|
|
case "calc":
|
2020-08-31 12:40:30 +02:00
|
|
|
return this.$t("Create a calc") as string;
|
2020-02-18 08:57:00 +01:00
|
|
|
case "visio":
|
2020-10-27 10:52:15 +01:00
|
|
|
return this.$t("Create a videoconference") as string;
|
2020-08-31 12:40:30 +02:00
|
|
|
default:
|
|
|
|
return "";
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-31 10:41:38 +02:00
|
|
|
async createLinkModal(): Promise<void> {
|
|
|
|
this.createLinkResourceModal = true;
|
|
|
|
await this.$nextTick();
|
|
|
|
this.$refs.modalNewResourceLinkInput.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
async createFolderModal(): Promise<void> {
|
2020-02-18 08:57:00 +01:00
|
|
|
this.newResource.type = "folder";
|
|
|
|
this.createResourceModal = true;
|
2022-03-31 10:41:38 +02:00
|
|
|
await this.$nextTick();
|
|
|
|
this.$refs.modalNewResourceInput.focus();
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
|
2022-03-31 10:41:38 +02:00
|
|
|
async createResourceFromProvider(provider: IProvider): Promise<void> {
|
2020-08-31 12:40:30 +02:00
|
|
|
this.newResource.resourceUrl = Resources.generateFullResourceUrl(provider);
|
2020-02-18 08:57:00 +01:00
|
|
|
this.newResource.type = provider.software;
|
|
|
|
this.createResourceModal = true;
|
2022-03-31 10:41:38 +02:00
|
|
|
await this.$nextTick();
|
|
|
|
this.$refs.modalNewResourceInput.focus();
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
|
2020-08-31 12:40:30 +02:00
|
|
|
static generateFullResourceUrl(provider: IProvider): string {
|
2020-02-18 08:57:00 +01:00
|
|
|
const randomString = [...Array(10)]
|
|
|
|
.map(() => Math.random().toString(36)[3])
|
|
|
|
.join("")
|
2020-11-30 10:24:11 +01:00
|
|
|
.replace(/(.|$)/g, (c) =>
|
|
|
|
c[!Math.round(Math.random()) ? "toString" : "toLowerCase"]()
|
|
|
|
);
|
2020-02-18 08:57:00 +01:00
|
|
|
switch (provider.type) {
|
|
|
|
case "ethercalc":
|
|
|
|
case "etherpad":
|
|
|
|
case "jitsi":
|
|
|
|
default:
|
|
|
|
return `${provider.endpoint}${randomString}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 12:40:30 +02:00
|
|
|
get createResourceButtonLabel(): string {
|
|
|
|
if (!this.newResource.type) return "";
|
2020-02-18 08:57:00 +01:00
|
|
|
return this.createSentenceForType(this.newResource.type);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Watch("checkedAll")
|
2020-08-31 12:40:30 +02:00
|
|
|
watchCheckedAll(): void {
|
2020-02-18 08:57:00 +01:00
|
|
|
this.resource.children.elements.forEach(({ id }) => {
|
|
|
|
if (!id) return;
|
|
|
|
this.checkedResources[id] = this.checkedAll;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@Watch("checkedResources", { deep: true })
|
|
|
|
watchValidCheckedResources(): string[] {
|
|
|
|
const validCheckedResources: string[] = [];
|
2020-08-31 12:40:30 +02:00
|
|
|
Object.entries(this.checkedResources).forEach(([key, value]) => {
|
2020-02-18 08:57:00 +01:00
|
|
|
if (value) {
|
|
|
|
validCheckedResources.push(key);
|
|
|
|
}
|
2020-08-31 12:40:30 +02:00
|
|
|
});
|
|
|
|
this.validCheckedResources = validCheckedResources;
|
|
|
|
return this.validCheckedResources;
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
|
2020-08-31 12:40:30 +02:00
|
|
|
async deleteMultipleResources(): Promise<void> {
|
|
|
|
this.validCheckedResources.forEach(async (resourceID) => {
|
2020-02-18 08:57:00 +01:00
|
|
|
await this.deleteResource(resourceID);
|
2020-08-31 12:40:30 +02:00
|
|
|
});
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
|
2020-10-19 19:21:39 +02:00
|
|
|
// eslint-disable-next-line class-methods-use-this
|
2021-08-02 18:11:22 +02:00
|
|
|
private postRefreshQueries(): InternalRefetchQueriesInclude {
|
2020-10-19 19:21:39 +02:00
|
|
|
return [
|
|
|
|
{
|
|
|
|
query: GET_RESOURCE,
|
|
|
|
variables: {
|
|
|
|
path: this.actualPath,
|
|
|
|
username: this.$route.params.preferredUsername,
|
2021-08-20 17:02:27 +02:00
|
|
|
page: this.page,
|
|
|
|
limit: this.RESOURCES_PER_PAGE,
|
2020-10-19 19:21:39 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2020-08-31 12:40:30 +02:00
|
|
|
async deleteResource(resourceID: string): Promise<void> {
|
2020-02-18 08:57:00 +01:00
|
|
|
try {
|
|
|
|
await this.$apollo.mutate({
|
|
|
|
mutation: DELETE_RESOURCE,
|
|
|
|
variables: {
|
|
|
|
id: resourceID,
|
|
|
|
},
|
2020-10-19 19:21:39 +02:00
|
|
|
refetchQueries: () => this.postRefreshQueries(),
|
2020-02-18 08:57:00 +01:00
|
|
|
});
|
2020-11-30 10:24:11 +01:00
|
|
|
this.validCheckedResources = this.validCheckedResources.filter(
|
|
|
|
(id) => id !== resourceID
|
|
|
|
);
|
2020-02-18 08:57:00 +01:00
|
|
|
delete this.checkedResources[resourceID];
|
2021-09-29 18:20:33 +02:00
|
|
|
} catch (e: any) {
|
2020-02-18 08:57:00 +01:00
|
|
|
console.error(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-31 10:41:38 +02:00
|
|
|
async handleRename(resource: IResource): Promise<void> {
|
2020-02-18 08:57:00 +01:00
|
|
|
this.renameModal = true;
|
2020-07-09 17:24:28 +02:00
|
|
|
this.updatedResource = { ...resource };
|
2022-03-31 10:41:38 +02:00
|
|
|
await this.$nextTick();
|
|
|
|
this.$refs.resourceRenameInput.focus();
|
|
|
|
this.$refs.resourceRenameInput.$el.querySelector("input").select();
|
2020-06-25 18:47:17 +02:00
|
|
|
}
|
|
|
|
|
2020-08-31 12:40:30 +02:00
|
|
|
handleMove(resource: IResource): void {
|
2020-06-25 18:47:17 +02:00
|
|
|
this.moveModal = true;
|
2020-07-09 17:24:28 +02:00
|
|
|
this.updatedResource = { ...resource };
|
2020-06-25 18:47:17 +02:00
|
|
|
}
|
|
|
|
|
2020-11-30 10:24:11 +01:00
|
|
|
async moveResource(
|
|
|
|
resource: IResource,
|
|
|
|
oldParent: IResource | undefined
|
|
|
|
): Promise<void> {
|
|
|
|
const parentPath =
|
|
|
|
oldParent && oldParent.path ? oldParent.path || "/" : "/";
|
2020-06-25 18:47:17 +02:00
|
|
|
await this.updateResource(resource, parentPath);
|
|
|
|
this.moveModal = false;
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
|
2020-08-31 12:40:30 +02:00
|
|
|
async renameResource(): Promise<void> {
|
2020-02-18 08:57:00 +01:00
|
|
|
await this.updateResource(this.updatedResource);
|
2020-06-25 18:47:17 +02:00
|
|
|
this.renameModal = false;
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
|
2020-11-30 10:24:11 +01:00
|
|
|
async updateResource(
|
|
|
|
resource: IResource,
|
|
|
|
parentPath: string | null = null
|
|
|
|
): Promise<void> {
|
2020-02-18 08:57:00 +01:00
|
|
|
try {
|
|
|
|
await this.$apollo.mutate<{ updateResource: IResource }>({
|
|
|
|
mutation: UPDATE_RESOURCE,
|
|
|
|
variables: {
|
|
|
|
id: resource.id,
|
|
|
|
title: resource.title,
|
2020-06-25 18:47:17 +02:00
|
|
|
parentId: resource.parent ? resource.parent.id : null,
|
2020-02-18 08:57:00 +01:00
|
|
|
path: resource.path,
|
|
|
|
},
|
2020-10-19 19:21:39 +02:00
|
|
|
refetchQueries: () => this.postRefreshQueries(),
|
2021-06-11 14:21:27 +02:00
|
|
|
update: (
|
|
|
|
store: ApolloCache<{ updateResource: IResource }>,
|
|
|
|
{ data }: FetchResult
|
|
|
|
) => {
|
2020-11-30 10:24:11 +01:00
|
|
|
if (!data || data.updateResource == null || parentPath == null)
|
|
|
|
return;
|
2020-06-25 18:47:17 +02:00
|
|
|
if (!this.resource.actor) return;
|
|
|
|
|
|
|
|
console.log("Removing ressource from old parent");
|
|
|
|
const oldParentCachedData = store.readQuery<{ resource: IResource }>({
|
|
|
|
query: GET_RESOURCE,
|
|
|
|
variables: {
|
|
|
|
path: parentPath,
|
|
|
|
username: this.resource.actor.preferredUsername,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
if (oldParentCachedData == null) return;
|
|
|
|
const { resource: oldParentCachedResource } = oldParentCachedData;
|
|
|
|
if (oldParentCachedResource == null) {
|
2020-11-30 10:24:11 +01:00
|
|
|
console.error(
|
|
|
|
"Cannot update resource cache, because of null value."
|
|
|
|
);
|
2020-06-25 18:47:17 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-08-31 12:40:30 +02:00
|
|
|
const updatedResource: IResource = data.updateResource;
|
2020-06-25 18:47:17 +02:00
|
|
|
|
2021-08-20 17:13:04 +02:00
|
|
|
const updatedElementList =
|
2021-05-17 19:01:08 +02:00
|
|
|
oldParentCachedResource.children.elements.filter(
|
|
|
|
(cachedResource) => cachedResource.id !== updatedResource.id
|
|
|
|
);
|
2020-06-25 18:47:17 +02:00
|
|
|
|
|
|
|
store.writeQuery({
|
|
|
|
query: GET_RESOURCE,
|
|
|
|
variables: {
|
|
|
|
path: parentPath,
|
|
|
|
username: this.resource.actor.preferredUsername,
|
|
|
|
},
|
2021-08-20 17:13:04 +02:00
|
|
|
data: {
|
|
|
|
resource: {
|
|
|
|
...oldParentCachedResource,
|
|
|
|
children: {
|
|
|
|
...oldParentCachedResource.children,
|
|
|
|
elements: [...updatedElementList],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-06-25 18:47:17 +02:00
|
|
|
});
|
|
|
|
console.log("Finished removing ressource from old parent");
|
|
|
|
|
|
|
|
console.log("Adding resource to new parent");
|
2020-08-31 12:40:30 +02:00
|
|
|
if (!updatedResource.parent || !updatedResource.parent.path) {
|
2020-06-25 18:47:17 +02:00
|
|
|
console.log("No cache found for new parent");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const newParentCachedData = store.readQuery<{ resource: IResource }>({
|
|
|
|
query: GET_RESOURCE,
|
|
|
|
variables: {
|
2020-08-31 12:40:30 +02:00
|
|
|
path: updatedResource.parent.path,
|
2020-06-25 18:47:17 +02:00
|
|
|
username: this.resource.actor.preferredUsername,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
if (newParentCachedData == null) return;
|
|
|
|
const { resource: newParentCachedResource } = newParentCachedData;
|
|
|
|
if (newParentCachedResource == null) {
|
2020-11-30 10:24:11 +01:00
|
|
|
console.error(
|
|
|
|
"Cannot update resource cache, because of null value."
|
|
|
|
);
|
2020-06-25 18:47:17 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
store.writeQuery({
|
|
|
|
query: GET_RESOURCE,
|
|
|
|
variables: {
|
2020-08-31 12:40:30 +02:00
|
|
|
path: updatedResource.parent.path,
|
2020-06-25 18:47:17 +02:00
|
|
|
username: this.resource.actor.preferredUsername,
|
|
|
|
},
|
2021-08-20 17:13:04 +02:00
|
|
|
data: {
|
|
|
|
resource: {
|
|
|
|
...newParentCachedResource,
|
|
|
|
children: {
|
|
|
|
...newParentCachedResource.children,
|
|
|
|
elements: [
|
|
|
|
...newParentCachedResource.children.elements,
|
|
|
|
resource,
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-06-25 18:47:17 +02:00
|
|
|
});
|
|
|
|
console.log("Finished adding resource to new parent");
|
|
|
|
},
|
2020-02-18 08:57:00 +01:00
|
|
|
});
|
2021-09-29 18:20:33 +02:00
|
|
|
} catch (e: any) {
|
2020-02-18 08:57:00 +01:00
|
|
|
console.error(e);
|
|
|
|
}
|
|
|
|
}
|
2020-10-19 19:21:39 +02:00
|
|
|
|
2021-06-14 15:12:38 +02:00
|
|
|
@Watch("page")
|
|
|
|
loadMoreResources(): void {
|
|
|
|
this.$apollo.queries.resource.fetchMore({
|
|
|
|
// New variables
|
|
|
|
variables: {
|
|
|
|
page: this.page,
|
|
|
|
limit: this.RESOURCES_PER_PAGE,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-10-19 19:21:39 +02:00
|
|
|
handleErrors(errors: any[]): void {
|
|
|
|
if (errors.some((error) => error.status_code === 404)) {
|
|
|
|
this.$router.replace({ name: RouteName.PAGE_NOT_FOUND });
|
|
|
|
}
|
|
|
|
}
|
2021-06-14 15:12:38 +02:00
|
|
|
|
|
|
|
async pushRouter(args: Record<string, string>): Promise<void> {
|
|
|
|
try {
|
|
|
|
const path = this.filteredPath.toString();
|
|
|
|
const routeName =
|
|
|
|
path === ""
|
|
|
|
? RouteName.RESOURCE_FOLDER_ROOT
|
|
|
|
: RouteName.RESOURCE_FOLDER;
|
|
|
|
|
|
|
|
await this.$router.push({
|
|
|
|
name: routeName,
|
|
|
|
params: { path },
|
|
|
|
query: { ...this.$route.query, ...args },
|
|
|
|
});
|
2021-09-29 18:20:33 +02:00
|
|
|
} catch (e: any) {
|
2021-06-14 15:12:38 +02:00
|
|
|
if (isNavigationFailure(e, NavigationFailureType.redirected)) {
|
|
|
|
throw Error(e.toString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-10 15:19:16 +01:00
|
|
|
|
|
|
|
get breadcrumbLinks() {
|
|
|
|
if (!this.resource?.actor) return [];
|
|
|
|
const resourceActor = this.resource.actor;
|
|
|
|
const links = [
|
|
|
|
{
|
|
|
|
name: RouteName.GROUP,
|
|
|
|
params: { preferredUsername: usernameWithDomain(this.resource.actor) },
|
|
|
|
text: displayName(this.resource.actor),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: RouteName.RESOURCE_FOLDER_ROOT,
|
|
|
|
params: { preferredUsername: usernameWithDomain(this.resource.actor) },
|
|
|
|
text: this.$t("Resources") as string,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
links.push(
|
|
|
|
...this.filteredPath.map((pathFragment, index) => {
|
|
|
|
return {
|
|
|
|
name: RouteName.RESOURCE_FOLDER,
|
|
|
|
params: {
|
|
|
|
path: ResourceMixin.resourcePathArray(this.resource).slice(
|
|
|
|
0,
|
|
|
|
index + 1
|
|
|
|
) as unknown as string,
|
|
|
|
preferredUsername: usernameWithDomain(resourceActor),
|
|
|
|
},
|
|
|
|
text: pathFragment,
|
|
|
|
};
|
|
|
|
})
|
|
|
|
);
|
|
|
|
return links;
|
|
|
|
}
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
2021-11-04 18:14:36 +01:00
|
|
|
@use "@/styles/_mixins" as *;
|
|
|
|
|
2020-10-19 19:21:39 +02:00
|
|
|
.container.section {
|
|
|
|
background: $white;
|
2021-06-14 15:12:38 +02:00
|
|
|
|
|
|
|
& > nav.pagination {
|
|
|
|
margin-top: 1rem;
|
|
|
|
}
|
2020-10-19 19:21:39 +02:00
|
|
|
}
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
nav.breadcrumb ul {
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
li:last-child .dropdown {
|
2021-11-04 18:14:36 +01:00
|
|
|
@include margin-left(5px);
|
2020-02-18 08:57:00 +01: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;
|
|
|
|
|
2021-06-14 15:13:08 +02:00
|
|
|
::v-deep .b-checkbox.checkbox {
|
2021-11-04 18:14:36 +01:00
|
|
|
@include margin-left(10px);
|
2021-06-14 15:13:08 +02:00
|
|
|
}
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
.actions {
|
2021-11-04 18:14:36 +01:00
|
|
|
@include margin-right(5px);
|
2020-02-18 08:57:00 +01:00
|
|
|
|
|
|
|
& > * {
|
2021-11-04 18:14:36 +01:00
|
|
|
@include margin-left(5px);
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.resource-item,
|
|
|
|
.new-resource-preview {
|
|
|
|
display: flex;
|
|
|
|
font-size: 14px;
|
|
|
|
border: 1px solid #c0cdd9;
|
|
|
|
border-radius: 4px;
|
|
|
|
color: #444b5d;
|
|
|
|
margin-top: 14px;
|
2021-06-14 15:13:08 +02:00
|
|
|
margin-bottom: 14px;
|
2020-02-18 08:57:00 +01:00
|
|
|
|
|
|
|
.resource-checkbox {
|
|
|
|
align-self: center;
|
2021-11-04 18:14:36 +01:00
|
|
|
@include padding-left(10px);
|
2020-02-18 08:57:00 +01:00
|
|
|
opacity: 0.3;
|
2021-06-14 15:13:08 +02:00
|
|
|
|
|
|
|
::v-deep .b-checkbox.checkbox {
|
2021-11-04 18:14:36 +01:00
|
|
|
@include margin-right(0.25rem);
|
2021-06-14 15:13:08 +02:00
|
|
|
}
|
2020-02-18 08:57:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
&:hover .resource-checkbox,
|
|
|
|
.resource-checkbox.checked {
|
|
|
|
opacity: 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|