mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-25 16:44:28 +00:00
652 lines
16 KiB
JavaScript
652 lines
16 KiB
JavaScript
import CoreForm from "../Form/Form.js";
|
|
import FormInput from "../Form/Input.js";
|
|
import BsModal from "../Bootstrap/Modal.js";
|
|
import AbstractWidget from './Abstract.js';
|
|
|
|
import ApiBookmark from '../../api/factory/widget/bookmark.js';
|
|
|
|
export default {
|
|
name: "WidgetsUrl",
|
|
mixins: [AbstractWidget],
|
|
inject: {
|
|
editModeIsActive: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
sectionName: {
|
|
type: String,
|
|
default: false
|
|
}
|
|
},
|
|
components:{
|
|
CoreForm,
|
|
FormInput,
|
|
BsModal,
|
|
PvChips: primevue.chips,
|
|
PvMultiSelect: primevue.multiselect,
|
|
PvAutoComplete: primevue.autocomplete,
|
|
},
|
|
data: () => ({
|
|
bookmark_id: null,
|
|
title_input: "",
|
|
url_input: "",
|
|
sort: null,
|
|
validation: {
|
|
invalidURL: false,
|
|
invalidTitel: false,
|
|
},
|
|
tagsArrayMS: [],
|
|
tagsArrayAC: [],
|
|
selectedTags: [],
|
|
newTag: null,
|
|
selectedFilters: [],
|
|
filter: [],
|
|
filteredArray: [],
|
|
sharedFiltered: {},
|
|
}),
|
|
computed: {
|
|
tagName() {
|
|
return this.config.tag !== undefined && this.config.tag.length > 0
|
|
? this.config.tag
|
|
: this.$p.t("bookmark", "myBookmarks");
|
|
},
|
|
emptyBookmarks() {
|
|
if (this.shared instanceof Array && this.shared.length == 0) return true;
|
|
|
|
if (!this.shared) return true;
|
|
|
|
return false;
|
|
},
|
|
newSort(){
|
|
if(this.shared.length == 0)
|
|
return 1;
|
|
else
|
|
return Math.max(...this.shared.map(b => b.sort)) + 1;
|
|
},
|
|
maxSort(){
|
|
if(this.shared.length == 0)
|
|
return 0;
|
|
else
|
|
return Math.max(...this.sharedFiltered.map(b => b.sort));
|
|
},
|
|
minSort(){
|
|
if(this.shared.length == 0)
|
|
return 0;
|
|
else
|
|
return Math.min(...this.sharedFiltered.map(b => b.sort));
|
|
},
|
|
filterInput(){
|
|
return this.selectedFilters.map(item => item.tag);
|
|
},
|
|
},
|
|
methods: {
|
|
stopDrag(event){
|
|
event.preventDefault();
|
|
},
|
|
clearInputs(){
|
|
this.title_input = "";
|
|
this.url_input = "";
|
|
this.selectedTags = [];
|
|
},
|
|
openCreateModal() {
|
|
this.$refs.createModal.show()
|
|
},
|
|
openEditModal(bookmark) {
|
|
this.title_input = bookmark.title;
|
|
this.url_input = bookmark.url;
|
|
this.bookmark_id = bookmark.bookmark_id;
|
|
this.selectedTags = JSON.parse(bookmark.tag);
|
|
|
|
this.$refs.editModal.show();
|
|
},
|
|
editBookmark(event){
|
|
event.preventDefault();
|
|
if(!this.bookmark_id || !this.url_input || !this.title_input) return;
|
|
|
|
this.$api
|
|
.call(ApiBookmark.update({
|
|
bookmark_id: this.bookmark_id,
|
|
title: this.title_input,
|
|
url: this.url_input,
|
|
tag: this.selectedTags,
|
|
}))
|
|
.then((res) => res.data)
|
|
.then((result) => {
|
|
this.$fhcAlert.alertInfo(this.$p.t("bookmark", "bookmarkUpdated"));
|
|
// refetch the bookmarks to see the updates
|
|
this.getAllBookmarkTags();
|
|
this.fetchBookmarks();
|
|
// reset the values for the title and url inputs
|
|
this.clearInputs();
|
|
this.$refs.editModal.hide();
|
|
this.bookmark_id = null;
|
|
})
|
|
.catch(this.$fhcAlert.handleSystemError);
|
|
},
|
|
|
|
insertBookmark(event) {
|
|
event.preventDefault();
|
|
|
|
// reset is-invalid css on url input field
|
|
for (let key of Object.keys(this.validation)) {
|
|
this.validation[key] = false;
|
|
}
|
|
|
|
// early return if validation failed
|
|
if (!this.isValidationSuccessfull()) return;
|
|
|
|
// get highest Sort
|
|
this.sort = this.newSort;
|
|
|
|
this.$api
|
|
.call(ApiBookmark.insert({
|
|
tag: this.selectedTags,
|
|
title: this.title_input,
|
|
url: this.url_input,
|
|
sort: this.sort
|
|
}))
|
|
.then((res) => res.data)
|
|
.then((result) => {
|
|
this.$fhcAlert.alertInfo(this.$p.t("bookmark", "bookmarkAdded"));
|
|
// refetch the bookmarks to see the updates
|
|
this.getAllBookmarkTags();
|
|
this.fetchBookmarks();
|
|
this.$refs.createModal.hide();
|
|
// reset the values for the title and url inputs
|
|
this.clearInputs();
|
|
})
|
|
.catch(this.$fhcAlert.handleSystemError);
|
|
},
|
|
isValidationSuccessfull() {
|
|
// validate the input fields
|
|
if (this.title_input.length === 0) {
|
|
this.$fhcAlert.alertError(this.$p.t("bookmark", "invalidTitel"));
|
|
this.validation.invalidTitel = true;
|
|
}
|
|
if (!URL.canParse(this.url_input)) {
|
|
this.$fhcAlert.alertError(this.$p.t("bookmark", "invalidUrl"));
|
|
this.validation.invalidURL = true;
|
|
}
|
|
|
|
return !Object.values(this.validation).some(value => value === true);
|
|
},
|
|
async fetchBookmarks() {
|
|
await this.$api
|
|
.call(ApiBookmark.getBookmarks())
|
|
.then((res) => res.data)
|
|
.then((result) => {
|
|
this.shared = result;
|
|
|
|
this.$nextTick(() => {
|
|
this.sharedFiltered = this.filterBookmarksByTags(this.shared);
|
|
});
|
|
})
|
|
.catch(this.$fhcAlert.handleSystemError);
|
|
},
|
|
async removeLink(bookmark_id) {
|
|
let isConfirmed = await this.$fhcAlert.confirmDelete();
|
|
|
|
// early return if the confirm dialog was not confirmed
|
|
if (!isConfirmed) return;
|
|
|
|
this.$api
|
|
.call(ApiBookmark.delete(bookmark_id))
|
|
.then((res) => res.data)
|
|
.then((result) => {
|
|
this.$fhcAlert.alertInfo(this.$p.t("bookmark", "bookmarkDeleted"));
|
|
// refetch the bookmarks to see the updates
|
|
this.fetchBookmarks();
|
|
this.getAllBookmarkTags();
|
|
})
|
|
.catch(this.$fhcAlert.handleSystemError);
|
|
},
|
|
filterBookmarksByTags(bookmarks) {
|
|
const filter = this.filter;
|
|
if (!filter || filter.length === 0 || filter == "[]") return bookmarks;
|
|
|
|
return bookmarks.filter(b => {
|
|
const tags = JSON.parse(b.tag || "[]");
|
|
return tags.some(tag => filter.includes(tag));
|
|
});
|
|
},
|
|
sortDown(bookmark_id){
|
|
const current = this.sharedFiltered.find(b => b.bookmark_id === bookmark_id);
|
|
|
|
const next = this.sharedFiltered
|
|
.filter(b => b.sort > current.sort)
|
|
.sort((a, b) => a.sort - b.sort)[0];
|
|
|
|
if (!next) {
|
|
console.log("lowest sort item, no change");
|
|
return;
|
|
}
|
|
this.changeOrder(current.bookmark_id, next.bookmark_id);
|
|
},
|
|
sortUp(bookmark_id){
|
|
const current = this.sharedFiltered.find(b => b.bookmark_id === bookmark_id);
|
|
|
|
const next = this.sharedFiltered
|
|
.filter(b => b.sort < current.sort)
|
|
.sort((a, b) => a.sort + b.sort)[0];
|
|
|
|
if (!next) {
|
|
console.log("highest sort item, no change");
|
|
return;
|
|
}
|
|
this.changeOrder(current.bookmark_id, next.bookmark_id);
|
|
},
|
|
changeOrder(bookmark_id_1, bookmark_id_2){
|
|
this.$api
|
|
.call(ApiBookmark.changeOrder(bookmark_id_1, bookmark_id_2))
|
|
.then((res) => res.data)
|
|
.then((result) => {
|
|
// refetch the bookmarks to see the updates
|
|
this.fetchBookmarks();
|
|
})
|
|
.catch(this.$fhcAlert.handleSystemError);
|
|
},
|
|
hasTags(link) {
|
|
if (!link || !link.tag) return false;
|
|
|
|
let tags = link.tag;
|
|
if (typeof tags === 'string') {
|
|
try {
|
|
tags = JSON.parse(tags)
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
if (Array.isArray(tags) && tags.length > 0) {
|
|
return tags.join(' ');
|
|
}
|
|
},
|
|
prepareTag(bookmarkArr){
|
|
const parsedArr = Array.isArray(bookmarkArr)
|
|
? bookmarkArr
|
|
: JSON.parse(bookmarkArr);
|
|
|
|
const result = parsedArr.map(tag => ({
|
|
tag,
|
|
code: tag
|
|
}));
|
|
|
|
return result;
|
|
},
|
|
getAllBookmarkTags(){
|
|
this.$api
|
|
.call(ApiBookmark.getAllBookmarkTags())
|
|
.then((res) => res.data)
|
|
.then((result) => {
|
|
//Version Chips
|
|
this.tagsArrayMS = this.prepareTag(result.data);
|
|
|
|
//Version Autocomplete
|
|
this.tagsArrayAC = result.data;
|
|
})
|
|
.catch(this.$fhcAlert.handleSystemError);
|
|
},
|
|
openFilterModal(){
|
|
this.$refs.filterModal.show();
|
|
},
|
|
async handleAddingTagFilter(widgetId){
|
|
|
|
const result = await this.isInOverride(widgetId);
|
|
|
|
if (!result) {
|
|
return;
|
|
}
|
|
const [status, reason] = result;
|
|
|
|
if (status) {
|
|
this.addTagFilter(widgetId);
|
|
} else {
|
|
this.addWidgetToOverride(widgetId, reason);
|
|
}
|
|
},
|
|
addTagFilter(widgetId){
|
|
this.$api
|
|
.call(ApiBookmark.addTagFilter(widgetId, this.sectionName, this.filterInput))
|
|
.then((res) => res.data)
|
|
.then((result) => {
|
|
this.$fhcAlert.alertInfo(this.$p.t("bookmark", "filterUpdated"));
|
|
this.$refs.filterModal.hide();
|
|
this.getTagFilter(this.widgetId);
|
|
|
|
this.$nextTick(() => {
|
|
this.getAllBookmarkTags();
|
|
});
|
|
this.fetchBookmarks();
|
|
})
|
|
.catch(this.$fhcAlert.handleSystemError);
|
|
},
|
|
async isInOverride(widgetId) {
|
|
try {
|
|
const res = await this.$api.call(ApiBookmark.isInOverride(widgetId, this.sectionName));
|
|
const result = res.data;
|
|
return result;
|
|
} catch (err) {
|
|
this.$fhcAlert.handleSystemError(err);
|
|
return null;
|
|
}
|
|
},
|
|
addWidgetToOverride(widgetId, reason){
|
|
this.$api
|
|
.call(ApiBookmark.addWidgetToOverride(
|
|
widgetId,
|
|
this.sectionName,
|
|
reason,
|
|
this.item_data.x,
|
|
this.item_data.y,
|
|
this.item_data.h,
|
|
this.item_data.w
|
|
))
|
|
.then((res) => res.data)
|
|
.then((result) => {
|
|
this.addTagFilter(widgetId);
|
|
})
|
|
.catch(this.$fhcAlert.handleSystemError);
|
|
},
|
|
getTagFilter(widget_id) {
|
|
this.$api
|
|
.call(ApiBookmark.getTagFilter(widget_id, this.sectionName))
|
|
.then((res) => res.data)
|
|
.then((result) => {
|
|
const rawTags = result.tags; // string
|
|
this.filter = rawTags;
|
|
if(rawTags != null) {
|
|
this.rawTagsParsed = JSON.parse(rawTags);
|
|
this.selectedFilters = this.prepareTag(this.rawTagsParsed);
|
|
}
|
|
this.fetchBookmarks();
|
|
})
|
|
.catch(this.$fhcAlert.handleSystemError);
|
|
},
|
|
search(event) {
|
|
const query = event.query ?? "";
|
|
|
|
// Filter for text
|
|
this.filteredArray = this.tagsArrayAC.filter(item =>
|
|
item.toLowerCase().includes(query.toLowerCase())
|
|
);
|
|
|
|
// input if search not successful
|
|
if (this.filteredArray.length === 0 && query) {
|
|
this.filteredArray = [query];
|
|
}
|
|
},
|
|
},
|
|
async mounted() {
|
|
if(this.widgetId) {
|
|
this.getTagFilter(this.widgetId);
|
|
}
|
|
await this.fetchBookmarks();
|
|
this.getAllBookmarkTags();
|
|
},
|
|
created() {
|
|
// this.$emit('setConfig', true); // -> use this to enable widget config mode if needed
|
|
},
|
|
|
|
|
|
template: /*html*/ `
|
|
<div class="widgets-url w-100 h-100 overflow-auto p-3">
|
|
|
|
<div class="d-flex mt-2">
|
|
<button
|
|
class="btn btn-outline-secondary btn-sm flex-grow-1 me-2"
|
|
@click="openCreateModal"
|
|
>
|
|
{{ $p.t('bookmark', 'newLink') }}
|
|
</button>
|
|
<button
|
|
v-if="selectedFilters.length"
|
|
class="btn btn-secondary btn-sm"
|
|
:title="$p.t('bookmark/editFilter')"
|
|
@click="openFilterModal"
|
|
>
|
|
<i class="fa-solid fa-filter-circle-xmark"></i>
|
|
</button>
|
|
<button
|
|
v-else
|
|
class="btn btn-outline-secondary btn-sm"
|
|
:title="$p.t('bookmark/filterByTags')"
|
|
@click="openFilterModal"
|
|
>
|
|
<i class="fa-solid fa-filter"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<template v-if="sharedFiltered">
|
|
|
|
<template v-if="!emptyBookmarks">
|
|
<div
|
|
v-for="link in sharedFiltered"
|
|
:key="link.id"
|
|
class="d-flex mt-2"
|
|
>
|
|
<a target="_blank" :href="link.url" class="me-1">
|
|
<i
|
|
class="fa fa-solid fa-arrow-up-right-from-square me-1"
|
|
aria-hidden="true"
|
|
></i>
|
|
{{ link.title }}
|
|
</a>
|
|
<span
|
|
v-if="hasTags(link)"
|
|
:title="hasTags(link)"
|
|
style="color: silver;"
|
|
>
|
|
<i
|
|
class="fa fa-solid fa-tag text-gray-500"
|
|
aria-hidden="true"
|
|
></i>
|
|
</span>
|
|
|
|
<div class="ms-auto">
|
|
<!--EDIT BOOKMARK-->
|
|
<a
|
|
type="button"
|
|
href="#"
|
|
@click.prevent="openEditModal(link)"
|
|
aria-label="edit bookmark"
|
|
:title="$p.t('bookmark/editBookmark')"
|
|
>
|
|
<i class="fa fa-edit me-1" aria-hidden="true"></i>
|
|
</a>
|
|
<!--DELETE BOOKMARK-->
|
|
<a
|
|
type="button"
|
|
id="deleteBookmark"
|
|
href="#"
|
|
aria-label="delete bookmark"
|
|
:title="$p.t('bookmark/deleteBookmark')"
|
|
@click.prevent="removeLink(link.bookmark_id)"
|
|
>
|
|
<i class="fa fa-regular fa-trash-can" aria-hidden="true"></i>
|
|
</a>
|
|
<!--SORT BOOKMARKS-->
|
|
<a
|
|
v-if="sharedFiltered.length > 1"
|
|
type="button"
|
|
id="downsortBookmark"
|
|
href="#"
|
|
aria-label="sortdown bookmark"
|
|
:title="$p.t('bookmark/sortDownwards')"
|
|
@click.prevent="sortDown(link.bookmark_id)"
|
|
>
|
|
<i
|
|
class="fa fa-arrow-down me-1"
|
|
:class="{ 'text-light pointer-events-none': link.sort === maxSort }"
|
|
></i>
|
|
</a>
|
|
<a
|
|
v-if="sharedFiltered.length > 1"
|
|
type="button"
|
|
id="upsortBookmark"
|
|
href="#"
|
|
aria-label="sortup bookmark"
|
|
:title="$p.t('bookmark/sortToTop')"
|
|
@click.prevent="sortUp(link.bookmark_id)"
|
|
>
|
|
<i
|
|
class="fa fa-arrow-up me-1"
|
|
:class="{ 'text-light pointer-events-none': link.sort === minSort }"
|
|
></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<div v-else class="d-flex mt-2">
|
|
<span>{{ $p.t('bookmark', 'emptyBookmarks') }}</span>
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<template v-else>
|
|
<p v-for="i in 4" class="placeholder-glow">
|
|
<span class="placeholder col-9"></span>
|
|
</p>
|
|
</template>
|
|
</div>
|
|
<!--EDIT MODAL-->
|
|
<teleport to="body">
|
|
<bs-modal
|
|
ref="editModal"
|
|
@hide-bs-modal="bookmark_id=null; clearInputs();"
|
|
>
|
|
<template #title>
|
|
<h2>{{ $p.t('bookmark', 'editLink') }}</h2>
|
|
</template>
|
|
<template #default>
|
|
|
|
<form-input
|
|
:label="$p.t('profil', 'Titel')"
|
|
:title="$p.t('profil','Titel')"
|
|
id="editTitle"
|
|
v-model="title_input"
|
|
name="title"
|
|
class="mb-2"
|
|
></form-input>
|
|
<form-input
|
|
label="Url"
|
|
title="Url"
|
|
id="editUrl"
|
|
v-model="url_input"
|
|
name="url"
|
|
></form-input>
|
|
|
|
<label class="mt-2">Tags</label>
|
|
<div class="mt-2">
|
|
<PvAutoComplete
|
|
v-model="selectedTags"
|
|
multiple
|
|
dropdown
|
|
:suggestions="filteredArray"
|
|
@complete="search"
|
|
/>
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<button @click="editBookmark" class="btn btn-primary">
|
|
{{ $p.t('bookmark', 'saveLink') }}
|
|
</button>
|
|
</template>
|
|
</bs-modal>
|
|
</teleport>
|
|
<!--CREATE MODAL-->
|
|
<teleport to="body">
|
|
<bs-modal ref="createModal" @hide-bs-modal="clearInputs();">
|
|
<template #title>
|
|
<h2>{{ $p.t('bookmark', 'newLink') }}</h2>
|
|
</template>
|
|
<template #default>
|
|
|
|
<form-input
|
|
:label="$p.t('profil', 'Titel')"
|
|
:title="$p.t('profil', 'Titel')"
|
|
id="insertTitle"
|
|
v-model="title_input"
|
|
name="title"
|
|
class="mb-2"
|
|
></form-input>
|
|
<form-input
|
|
label="Url"
|
|
title="Url"
|
|
id="insertUrl"
|
|
v-model="url_input"
|
|
name="url"
|
|
></form-input>
|
|
|
|
<label class="mt-2">Tags</label>
|
|
<div class="mt-2">
|
|
<PvAutoComplete
|
|
v-model="selectedTags"
|
|
multiple
|
|
dropdown
|
|
:suggestions="filteredArray"
|
|
@complete="search"
|
|
/>
|
|
</div>
|
|
|
|
</template>
|
|
<template #footer>
|
|
<button @click="insertBookmark" class="btn btn-primary">
|
|
{{ $p.t('bookmark', 'saveLink') }}
|
|
</button>
|
|
</template>
|
|
</bs-modal>
|
|
</teleport>
|
|
<!--FILTER MODAL-->
|
|
<teleport to="body">
|
|
<bs-modal ref="filterModal" @hide-bs-modal="clearInputs();">
|
|
<template #title>
|
|
<h2>{{ $p.t('bookmark', 'headerFilterBookmark') }}</h2>
|
|
</template>
|
|
<template #default>
|
|
|
|
<div class="mt-2 row">
|
|
<div class="col-10">
|
|
<PvMultiSelect
|
|
v-model="selectedFilters"
|
|
id="tagFilterUrl"
|
|
:options="tagsArrayMS"
|
|
optionLabel="tag"
|
|
display="chip"
|
|
:placeholder="$p.t('bookmark', 'noFilter')"
|
|
:maxSelectedLabels="3"
|
|
class="p-inputtext-sm w-100 me-2"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
</template>
|
|
<template #footer>
|
|
<button
|
|
class="btn btn-secondary"
|
|
@click="handleAddingTagFilter(widgetId)"
|
|
:title="$p.t('bookmark', 'filterByTags')"
|
|
>
|
|
OK
|
|
</button>
|
|
</template>
|
|
</bs-modal>
|
|
</teleport>
|
|
`,
|
|
};
|
|
|
|
|
|
/*
|
|
Link JSON structure:
|
|
{
|
|
"bookmark_id": number,
|
|
"uid": string,
|
|
"url": string,
|
|
"title": string,
|
|
"tag": string,
|
|
"insertamum": "2024-07-30 14:33:03.699318",
|
|
"insertvon": null,
|
|
"updateamum": null,
|
|
"updatevon": null
|
|
}
|
|
*/
|