Fix lint issues, update deps

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2022-09-20 16:53:26 +02:00
parent 86ca52c2cb
commit 151a7e54ae
61 changed files with 1533 additions and 1579 deletions

View File

@@ -153,7 +153,7 @@ body {
/* Modal */
.modal-content {
@apply bg-white dark:bg-zinc-800 rounded px-2 py-4 w-full;
@apply bg-white dark:bg-zinc-800 rounded px-2 py-4 w-full z-0;
}
/* Switch */
@@ -210,7 +210,7 @@ button.menubar__button {
/* Table */
.table tr {
@apply odd:bg-white dark:odd:bg-zinc-600 even:bg-gray-50 dark:even:bg-zinc-700 border-b rounded;
@apply odd:bg-white dark:odd:bg-zinc-600 last:border-b-0 even:bg-gray-50 dark:even:bg-zinc-700 border-b rounded;
}
.table-td {

View File

@@ -8,6 +8,5 @@
border: 2px solid;
z-index: 2;
flex-shrink: 0;
}
}

View File

@@ -62,9 +62,10 @@ const mentionOptions: MentionOptions = {
let popup: any;
return {
onStart: (props: any) => {
onStart: (props: Record<string, any>) => {
component = new VueRenderer(MentionList, {
propsData: props,
props,
editor: props.editor,
});
popup = tippy("body", {

View File

@@ -20,7 +20,7 @@ import { ref, watch } from "vue";
const props = defineProps<{
items: IPerson[];
command: ({ id }: { id: string }) => {};
command: ({ id }: { id: string }) => any;
}>();
// @Prop({ type: Function, required: true }) command!: any;
@@ -31,37 +31,37 @@ watch(props.items, () => {
selectedIndex.value = 0;
});
const onKeyDown = ({ event }: { event: KeyboardEvent }): boolean => {
if (event.key === "ArrowUp") {
upHandler();
return true;
}
// const onKeyDown = ({ event }: { event: KeyboardEvent }): boolean => {
// if (event.key === "ArrowUp") {
// upHandler();
// return true;
// }
if (event.key === "ArrowDown") {
downHandler();
return true;
}
// if (event.key === "ArrowDown") {
// downHandler();
// return true;
// }
if (event.key === "Enter") {
enterHandler();
return true;
}
// if (event.key === "Enter") {
// enterHandler();
// return true;
// }
return false;
};
// return false;
// };
const upHandler = (): void => {
selectedIndex.value =
(selectedIndex.value + props.items.length - 1) % props.items.length;
};
// const upHandler = (): void => {
// selectedIndex.value =
// (selectedIndex.value + props.items.length - 1) % props.items.length;
// };
const downHandler = (): void => {
selectedIndex.value = (selectedIndex.value + 1) % props.items.length;
};
// const downHandler = (): void => {
// selectedIndex.value = (selectedIndex.value + 1) % props.items.length;
// };
const enterHandler = (): void => {
selectItem(selectedIndex.value);
};
// const enterHandler = (): void => {
// selectItem(selectedIndex.value);
// };
const selectItem = (index: number): void => {
const item = props.items[index];

View File

@@ -1,149 +0,0 @@
<template>
<div class="address-autocomplete">
<!-- <o-field expanded>
<o-autocomplete
:data="addressData"
v-model="queryText"
:placeholder="$t('e.g. 10 Rue Jangot')"
field="fullName"
:loading="isFetching"
@typing="fetchAsyncData"
icon="map-marker"
expanded
@select="updateSelected"
v-bind="$attrs"
dir="auto"
>
<template #default="{ option }">
<o-icon :icon="option.poiInfos.poiIcon.icon" />
<b>{{ option.poiInfos.name }}</b
><br />
<small>{{ option.poiInfos.alternativeName }}</small>
</template>
</o-autocomplete>
</o-field>
<o-field
v-if="canDoGeoLocation"
:message="fieldErrors"
:type="{ 'is-danger': fieldErrors.length }"
>
<o-button
type="is-text"
v-if="!gettingLocation"
icon-right="target"
@click="locateMe"
@keyup.enter="locateMe"
>{{ $t("Use my location") }}</o-button
>
<span v-else>{{ $t("Getting location") }}</span>
</o-field> -->
<!--
<div v-if="selected && selected.geom" class="control">
<o-checkbox @input="togglemap" />
<label class="label">{{ $t("Show map") }}</label>
</div>
<div class="map" v-if="showmap && selected && selected.geom">
<map-leaflet
:coords="selected.geom"
:marker="{
text: [selected.poiInfos.name, selected.poiInfos.alternativeName],
icon: selected.poiInfos.poiIcon.icon,
}"
:updateDraggableMarkerCallback="reverseGeoCode"
:options="{ zoom: mapDefaultZoom }"
:readOnly="false"
/>
</div>
-->
</div>
</template>
<script lang="ts">
import { Prop, Watch, Vue } from "vue-property-decorator";
import { Address, IAddress } from "../../types/address.model";
// import AddressAutoCompleteMixin from "@/mixins/AddressAutoCompleteMixin";
// @Component({
// inheritAttrs: false,
// })
export default class AddressAutoComplete extends Vue {
@Prop({ required: false, default: false }) type!: string | false;
@Prop({ required: false, default: true, type: Boolean })
doGeoLocation!: boolean;
addressData: IAddress[] = [];
selected: IAddress = new Address();
initialQueryText = "";
addressModalActive = false;
showmap = false;
get queryText2(): string {
if (this.value !== undefined) {
return new Address(this.value).fullName;
}
return this.initialQueryText;
}
set queryText2(queryText: string) {
this.initialQueryText = queryText;
}
@Watch("value")
updateEditing(): void {
if (!this.value?.id) return;
this.selected = this.value;
}
updateSelected(option: IAddress): void {
if (option == null) return;
this.selected = option;
// this.$emit("input", this.selected);
}
resetPopup(): void {
this.selected = new Address();
}
openNewAddressModal(): void {
this.resetPopup();
this.addressModalActive = true;
}
togglemap(): void {
this.showmap = !this.showmap;
}
get canDoGeoLocation(): boolean {
return this.isSecureContext && this.doGeoLocation;
}
}
</script>
<style lang="scss">
.address-autocomplete {
margin-bottom: 0.75rem;
}
.autocomplete {
.dropdown-menu {
z-index: 2000;
}
.dropdown-item.is-disabled {
opacity: 1 !important;
cursor: auto;
}
}
.read-only {
cursor: pointer;
}
.map {
height: 400px;
width: 100%;
}
</style>

View File

@@ -150,7 +150,7 @@ import RouteName from "../../router/name";
import InlineAddress from "@/components/Address/InlineAddress.vue";
import { computed, inject } from "vue";
import MobilizonTag from "@/components/Tag.vue";
import MobilizonTag from "@/components/TagElement.vue";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import Video from "vue-material-design-icons/Video.vue";
import { formatDateTimeForEvent } from "@/utils/datetime";

View File

@@ -84,7 +84,7 @@
</template>
<a
target="_blank"
class="hover:underline"
class="underline"
rel="noopener noreferrer ugc"
:href="event.onlineAddress"
:title="

View File

@@ -130,7 +130,7 @@ import InlineAddress from "@/components/Address/InlineAddress.vue";
import Video from "vue-material-design-icons/Video.vue";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import AccountMultiple from "vue-material-design-icons/AccountMultiple.vue";
import Tag from "@/components/Tag.vue";
import Tag from "@/components/TagElement.vue";
withDefaults(
defineProps<{

View File

@@ -301,7 +301,7 @@ import {
organizerAvatarUrl,
organizerDisplayName,
} from "@/types/event.model";
import { displayNameAndUsername, IActor, IPerson } from "@/types/actor";
import { displayNameAndUsername, IPerson } from "@/types/actor";
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
import RouteName from "@/router/name";
import { changeIdentity } from "@/utils/identity";
@@ -323,29 +323,12 @@ import { useI18n } from "vue-i18n";
import { Dialog } from "@/plugins/dialog";
import { Snackbar } from "@/plugins/snackbar";
import { useDeleteEvent } from "@/composition/apollo/event";
import Tag from "@/components/Tag.vue";
import Tag from "@/components/TagElement.vue";
const defaultOptions: IEventCardOptions = {
hideDate: true,
loggedPerson: false,
hideDetails: false,
organizerActor: null,
};
const props = withDefaults(
defineProps<{
participation: IParticipant;
options?: IEventCardOptions;
}>(),
{
options: () => ({
hideDate: true,
loggedPerson: false,
hideDetails: false,
organizerActor: null,
}),
}
);
const props = defineProps<{
participation: IParticipant;
options?: IEventCardOptions;
}>();
const emit = defineEmits(["eventDeleted"]);
@@ -353,10 +336,6 @@ const { result: currentActorResult } = useQuery(CURRENT_ACTOR_CLIENT);
const currentActor = computed(() => currentActorResult.value?.currentActor);
const { t } = useI18n({ useScope: "global" });
const mergedOptions = computed<IEventCardOptions>(() => {
return { ...defaultOptions, ...props.options };
});
const dialog = inject<Dialog>("dialog");
const openDeleteEventModal = (
@@ -441,9 +420,8 @@ onDeleteEventError((error) => {
* Delete the event
*/
const openDeleteEventModalWrapper = () => {
openDeleteEventModal(
props.participation.event,
deleteEvent(props.participation.event)
openDeleteEventModal(props.participation.event, (event: IEvent) =>
deleteEvent({ eventId: event.id ?? "" })
);
};
@@ -474,15 +452,15 @@ const gotToWithCheck = async (
return router.push(route);
};
const organizerActor = computed<IActor | undefined>(() => {
if (
props.participation.event.attributedTo &&
props.participation.event.attributedTo.id
) {
return props.participation.event.attributedTo;
}
return props.participation.event.organizerActor;
});
// const organizerActor = computed<IActor | undefined>(() => {
// if (
// props.participation.event.attributedTo &&
// props.participation.event.attributedTo.id
// ) {
// return props.participation.event.attributedTo;
// }
// return props.participation.event.organizerActor;
// });
const seatsLeft = computed<number | null>(() => {
if (props.participation.event.options.maximumAttendeeCapacity > 0) {

View File

@@ -143,7 +143,7 @@ const props = withDefaults(
}
);
const addressModalActive = ref(false);
// const addressModalActive = ref(false);
const componentId = 0;
@@ -186,14 +186,14 @@ const updateSelected = (option: IAddress): void => {
emit("update:modelValue", selected.value);
};
const resetPopup = (): void => {
selected.value = new Address();
};
// const resetPopup = (): void => {
// selected.value = new Address();
// };
const openNewAddressModal = (): void => {
resetPopup();
addressModalActive.value = true;
};
// const openNewAddressModal = (): void => {
// resetPopup();
// addressModalActive.value = true;
// };
const checkCurrentPosition = (e: LatLng): boolean => {
if (!selected.value?.geom) return false;

View File

@@ -94,7 +94,7 @@ import ExitToApp from "vue-material-design-icons/ExitToApp.vue";
import DotsHorizontal from "vue-material-design-icons/DotsHorizontal.vue";
import AccountGroup from "vue-material-design-icons/AccountGroup.vue";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import Tag from "@/components/Tag.vue";
import Tag from "@/components/TagElement.vue";
import { htmlToText } from "@/utils/html";
import { useI18n } from "vue-i18n";

View File

@@ -8,6 +8,8 @@
@click="clickMap"
@update:zoom="updateZoom"
:options="{ zoomControl: false }"
ref="mapComponent"
@ready="onMapReady"
>
<l-tile-layer :url="tiles?.endpoint" :attribution="attribution">
</l-tile-layer>
@@ -16,28 +18,36 @@
:zoomInTitle="$t('Zoom in')"
:zoomOutTitle="$t('Zoom out')"
></l-control-zoom>
<!-- <v-locatecontrol
v-if="canDoGeoLocation"
:options="{ icon: 'mdi mdi-map-marker' }"
/> -->
<l-marker
:lat-lng="[lat, lon]"
@add="openPopup"
@update:latLng="updateDraggableMarkerPosition"
:draggable="!readOnly"
>
<l-popup v-if="popupMultiLine">
<l-icon>
<MapMarker :size="48" class="text-mbz-purple" />
</l-icon>
<l-popup v-if="popupMultiLine" :options="{ offset: new Point(22, 8) }">
<span v-for="line in popupMultiLine" :key="line"
>{{ line }}<br
/></span>
</l-popup>
</l-marker>
</l-map>
<CrosshairsGps ref="locationIcon" class="hidden" />
</div>
</template>
<script lang="ts" setup>
import { Icon, LatLng, LeafletMouseEvent, LeafletEvent } from "leaflet";
import {
LatLng,
LeafletMouseEvent,
LeafletEvent,
Control,
DomUtil,
Map,
Point,
} from "leaflet";
import "leaflet/dist/leaflet.css";
import {
LMap,
@@ -47,10 +57,12 @@ import {
LIcon,
LControlZoom,
} from "@vue-leaflet/vue-leaflet";
// import Vue2LeafletLocateControl from "@/components/Map/Vue2LeafletLocateControl.vue";
import { computed, nextTick, onMounted, ref } from "vue";
import { useMapTiles } from "@/composition/apollo/config";
import { useI18n } from "vue-i18n";
import Locatecontrol from "leaflet.locatecontrol";
import CrosshairsGps from "vue-material-design-icons/CrosshairsGps.vue";
import MapMarker from "vue-material-design-icons/MapMarker.vue";
const props = withDefaults(
defineProps<{
@@ -77,8 +89,18 @@ const defaultOptions: {
const zoom = ref(defaultOptions.zoom);
onMounted(() => {
const mapComponent = ref();
const mapObject = ref<Map>();
const locateControl = ref<Control.Locate>();
const locationIcon = ref();
const locationIconHTML = computed(() => locationIcon.value?.$el.innerHTML);
onMounted(async () => {
// this part resolve an issue where the markers would not appear
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line no-underscore-dangle
// delete Icon.Default.prototype._getIconUrl;
// Icon.Default.mergeOptions({
// iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
@@ -87,6 +109,50 @@ onMounted(() => {
// });
});
const onMapReady = async () => {
mapObject.value = mapComponent.value.leafletObject;
mountLocateControl();
};
const mountLocateControl = () => {
if (canDoGeoLocation.value && mapObject.value) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
locateControl.value = new Locatecontrol({
strings: { title: t("Show me where I am") as string },
position: "topleft",
drawCircle: false,
drawMarker: false,
createButtonCallback(container: HTMLElement | undefined, options: any) {
const link = DomUtil.create(
"a",
"leaflet-bar-part leaflet-bar-part-single",
container
);
link.title = options.strings.title;
link.href = "#";
link.setAttribute("role", "button");
const icon = DomUtil.create(
"span",
"material-design-icon rss-icon",
link
);
icon.setAttribute("aria-hidden", "true");
icon.setAttribute("role", "img");
icon.insertAdjacentHTML("beforeend", locationIconHTML.value);
console.log("icon for location", {
link,
icon,
});
return { link, icon };
},
...props.options,
}) as Control.Locate;
locateControl.value?.addTo(mapObject.value);
}
};
const openPopup = async (event: LeafletEvent): Promise<void> => {
await nextTick();
event.target.openPopup();
@@ -147,3 +213,6 @@ div.map-container {
}
}
</style>
<style>
@import "leaflet.locatecontrol/dist/L.Control.Locate.css";
</style>

View File

@@ -1,64 +0,0 @@
<template>
<div style="display: none">
<slot v-if="ready"></slot>
</div>
</template>
<script lang="ts">
/**
* Fork of https://github.com/domoritz/leaflet-locatecontrol
* to try to trigger location manually (not done ATM)
*/
import { DomEvent } from "leaflet";
// import { findRealParent, propsBinder } from "vue2-leaflet";
import Locatecontrol from "leaflet.locatecontrol";
import { Component, Prop, Vue } from "vue-property-decorator";
@Component({
beforeDestroy() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.parentContainer.removeLayer(this);
},
})
export default class Vue2LeafletLocateControl extends Vue {
@Prop({ type: Object, default: () => ({}) }) options!: Record<
string,
unknown
>;
@Prop({ type: Boolean, default: true }) visible!: boolean;
ready = false;
mapObject!: any;
parentContainer: any;
mounted(): void {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.mapObject = new Locatecontrol({
...this.options,
strings: { title: this.$t("Show me where I am") as string },
});
DomEvent.on(this.mapObject, this.$listeners as any);
propsBinder(this, this.mapObject, this.$props);
this.ready = true;
this.parentContainer = findRealParent(this.$parent);
this.mapObject.addTo(this.parentContainer.mapObject, !this.visible);
this.$nextTick(() => {
this.$emit("ready", this.mapObject);
});
}
public locate(): void {
this.mapObject.start();
}
}
</script>
<style>
/* @import "~leaflet.locatecontrol/dist/L.Control.Locate.css"; */
</style>

View File

@@ -86,6 +86,7 @@ import { IMedia } from "@/types/media.model";
import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import Upload from "vue-material-design-icons/Upload.vue";
import { formatBytes } from "@/utils/datetime";
const { t } = useI18n({ useScope: "global" });
@@ -140,14 +141,4 @@ watch(imageSrc, () => {
const showImageLoadingError = (): void => {
imagePreviewLoadingError.value = true;
};
// https://gist.github.com/zentala/1e6f72438796d74531803cc3833c039c
const formatBytes = (bytes: number, decimals?: number): string => {
if (bytes == 0) return "0 Bytes";
const k = 1024,
dm = decimals || 2,
sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
};
</script>

View File

@@ -61,7 +61,7 @@ import { formatDateTimeString } from "@/filters/datetime";
import Tag from "vue-material-design-icons/Tag.vue";
import AccountEdit from "vue-material-design-icons/AccountEdit.vue";
import Clock from "vue-material-design-icons/Clock.vue";
import MbzTag from "@/components/Tag.vue";
import MbzTag from "@/components/TagElement.vue";
const props = withDefaults(
defineProps<{

View File

@@ -22,12 +22,17 @@
</div>
<div class="">
<div class="" v-if="comment">
<article class="media">
<div class="media-left">
<figure class="image is-48x48" v-if="comment?.actor?.avatar">
<img :src="comment.actor.avatar.url" alt="" />
<article class="">
<div class="">
<figure class="h-8 w-8" v-if="comment?.actor?.avatar">
<img
:src="comment.actor.avatar.url"
alt=""
width="48"
height="48"
/>
</figure>
<o-icon v-else size="large" icon="account-circle" />
<AccountCircle v-else :size="48" />
</div>
<div class="">
<div class="prose dark:prose-invert">
@@ -65,7 +70,7 @@
</section>
<footer class="flex gap-2 py-3">
<o-button ref="cancelButton" @click="close">
<o-button ref="cancelButton" outlined @click="close">
{{ translatedCancelText }}
</o-button>
<o-button

View File

@@ -75,7 +75,7 @@ import ResourceItem from "@/components/Resource/ResourceItem.vue";
import FolderItem from "@/components/Resource/FolderItem.vue";
import { ref, watch } from "vue";
import { IResource } from "@/types/resource";
import Draggable from "@xiaoshuapp/draggable";
import Draggable from "vuedraggable";
import { IGroup } from "@/types/actor";
const props = withDefaults(
@@ -124,13 +124,13 @@ watch(checkedAll, () => {
});
});
const deleteResource = (resourceID: string) => {
validCheckedResources.value = validCheckedResources.value.filter(
(id) => id !== resourceID
);
delete checkedResources.value[resourceID];
emit("delete", resourceID);
};
// const deleteResource = (resourceID: string) => {
// validCheckedResources.value = validCheckedResources.value.filter(
// (id) => id !== resourceID
// );
// delete checkedResources.value[resourceID];
// emit("delete", resourceID);
// };
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;

View File

@@ -18,7 +18,7 @@
formatDateTimeString(resource.updatedAt?.toString())
}}</span>
</div>
<!-- <draggable
<draggable
v-if="!inline"
class="dropzone"
v-model="list"
@@ -26,7 +26,7 @@
:sort="false"
:group="groupObject"
@change="onChange"
/> -->
/>
</router-link>
<resource-dropdown
class="actions"
@@ -39,18 +39,18 @@
</template>
<script lang="ts" setup>
import { useRouter } from "vue-router";
// import Draggable, { ChangeEvent } from "@xiaoshuapp/draggable";
// import { SnackbarProgrammatic as Snackbar } from "buefy";
import Draggable, { ChangeEvent } from "vuedraggable";
import { IResource } from "@/types/resource";
import RouteName from "@/router/name";
import { IGroup, usernameWithDomain } from "@/types/actor";
import ResourceDropdown from "./ResourceDropdown.vue";
import { UPDATE_RESOURCE } from "@/graphql/resources";
import { ref } from "vue";
import { inject, ref } from "vue";
import { formatDateTimeString } from "@/filters/datetime";
import { useMutation } from "@vue/apollo-composable";
import { resourcePathArray } from "@/components/Resource/utils";
import Folder from "vue-material-design-icons/Folder.vue";
import { Snackbar } from "@/plugins/snackbar";
const props = withDefaults(
defineProps<{
@@ -77,7 +77,7 @@ const groupObject: Record<string, unknown> = {
const onChange = async (evt: ChangeEvent<IResource>) => {
if (evt.added && evt.added.element) {
const movedResource = evt.added.element as IResource;
// const movedResource = evt.added.element as IResource;
moveResource({
id: props.resource.id,
path: `${props.resource.path}/${props.resource.title}`,
@@ -100,19 +100,20 @@ onMovedResource(({ data }) => {
return router.push({
name: RouteName.RESOURCE_FOLDER,
params: {
path: ResourceMixin.resourcePathArray(props.resource),
path: resourcePathArray(props.resource),
preferredUsername: props.group.preferredUsername,
},
});
}
});
const snackbar = inject<Snackbar>("snackbar");
onMovedResourceError((e) => {
// Snackbar.open({
// message: e.message,
// variant: "danger",
// position: "bottom",
// });
snackbar?.open({
message: e.message,
variant: "danger",
position: "bottom",
});
return undefined;
});
</script>

View File

@@ -60,7 +60,7 @@
<script lang="ts" setup>
import { IResource, mapServiceTypeToIcon } from "@/types/resource";
import ResourceDropdown from "@/components/Resource/ResourceDropdown.vue";
import { ref, computed } from "vue";
import { computed } from "vue";
import { formatDateTimeString } from "@/filters/datetime";
import Link from "vue-material-design-icons/Link.vue";

View File

@@ -2,13 +2,13 @@
<div v-if="loggedUser">
<section>
<div class="setting-title">
<h2>{{ $t("Settings") }}</h2>
<h2>{{ t("Settings") }}</h2>
</div>
<div>
<h3>{{ $t("Language") }}</h3>
<h3>{{ t("Language") }}</h3>
<p>
{{
$t(
t(
"This setting will be used to display the website and send you emails in the correct language."
)
}}
@@ -17,7 +17,7 @@
<o-select
:loading="loading"
v-model="locale"
:placeholder="$t('Select a language')"
:placeholder="t('Select a language')"
>
<option v-for="(language, lang) in langs" :value="lang" :key="lang">
{{ language }}
@@ -27,15 +27,15 @@
</div>
<div>
<h3>{{ $t("Timezone") }}</h3>
<h3>{{ t("Timezone") }}</h3>
<p>
{{
$t(
t(
"We use your timezone to make sure you get notifications for an event at the correct time."
)
}}
{{
$t("Your timezone was detected as {timezone}.", {
t("Your timezone was detected as {timezone}.", {
timezone,
})
}}
@@ -43,7 +43,7 @@
variant="danger"
v-if="!loading && !supportedTimezone"
>
{{ $t("Your timezone {timezone} isn't supported.", { timezone }) }}
{{ t("Your timezone {timezone} isn't supported.", { timezone }) }}
</o-notification>
</p>
</div>
@@ -71,14 +71,14 @@ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const { loggedUser } = useUserSettings();
onMounted(() => {
updateLocale(locale.value);
updateLocale(locale.value as string);
doUpdateSetting({ timezone });
});
watch(locale, () => {
if (locale.value) {
updateLocale(locale.value);
saveLocaleData(locale.value);
updateLocale(locale.value as string);
saveLocaleData(locale.value as string);
}
});

View File

@@ -272,11 +272,11 @@ const isBasicMode = computed((): boolean => {
return props.mode === "basic";
});
const insertMention = (obj: { range: any; attrs: any }) => {
console.debug("initialize Mention");
};
// const insertMention = (obj: { range: any; attrs: any }) => {
// console.debug("initialize Mention");
// };
const observer = ref<MutationObserver | null>(null);
// const observer = ref<MutationObserver | null>(null);
onMounted(() => {
editor.value = new Editor({

View File

@@ -24,14 +24,14 @@
</div>
</template>
<script lang="ts" setup>
// import { SnackbarProgrammatic as Snackbar } from "buefy";
import { ITodo } from "../../types/todos";
import RouteName from "../../router/name";
import { UPDATE_TODO } from "../../graphql/todos";
import { computed, ref } from "vue";
import { computed, inject, ref } from "vue";
import { useMutation } from "@vue/apollo-composable";
import Account from "vue-material-design-icons/Account.vue";
import { formatDateString } from "@/filters/datetime";
import { Snackbar } from "@/plugins/snackbar";
const props = defineProps<{ todo: ITodo }>();
@@ -41,8 +41,8 @@ const status = computed({
get() {
return props.todo.status;
},
set(status: boolean) {
updateTodo({ status, id: props.todo.id });
set(newStatus: boolean) {
updateTodo({ status: newStatus, id: props.todo.id });
},
});
@@ -52,11 +52,13 @@ onDone(() => {
editMode.value = false;
});
const snackbar = inject<Snackbar>("snackbar");
onError((e) => {
// Snackbar.open({
// message: e.message,
// variant: "danger",
// position: "bottom",
// });
snackbar?.open({
message: e.message,
variant: "danger",
position: "bottom",
});
});
</script>

View File

@@ -1,102 +0,0 @@
<template>
<div v-if="attached && closable" class="tags has-addons">
<span class="tag" :class="[variant, size, { rounded }]">
<!-- <o-icon
v-if="icon"
:icon="icon"
:size="size"
:type="iconType"
/> -->
<span :class="{ 'has-ellipsis': ellipsis }" @click="click">
<slot />
</span>
</span>
<a
class="tag"
role="button"
:aria-label="ariaCloseLabel"
:tabindex="tabstop ? 0 : undefined"
:disabled="disabled"
:class="[
size,
closeType,
{ 'is-rounded': rounded },
closeIcon ? 'has-delete-icon' : 'is-delete',
]"
@click="close"
@keyup.delete.prevent="close"
>
<!-- <o-icon
custom-class=""
v-if="closeIcon"
:icon="closeIcon"
:size="size"
:type="closeIconType"
/> -->
</a>
</div>
<span v-else class="tag" :class="[variant, size, { 'is-rounded': rounded }]">
<!-- <o-icon
v-if="icon"
:icon="icon"
:size="size"
:type="iconType"
/> -->
<span :class="{ 'has-ellipsis': ellipsis }" @click="click">
<slot />
</span>
<a
v-if="closable"
role="button"
:aria-label="ariaCloseLabel"
class="delete is-small"
:class="closeType"
:disabled="disabled"
:tabindex="tabstop ? 0 : undefined"
@click="close"
@keyup.delete.prevent="close"
/>
</span>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
attached?: boolean;
closable?: boolean;
variant?: string;
size?: string;
rounded?: boolean;
disabled?: boolean;
ellipsis?: boolean;
tabstop?: boolean;
ariaCloseLabel?: string;
icon?: string;
iconType?: string;
closeType?: string;
closeIcon?: string;
closeIconType?: string;
}>(),
{
tabstop: true,
}
);
const emit = defineEmits(["close", "click"]);
/**
* Emit close event when delete button is clicked
* or delete key is pressed.
*/
const close = (event: Event) => {
if (props.disabled) return;
emit("close", event);
};
/**
* Emit click event when tag is clicked.
*/
const click = (event: Event) => {
if (props.disabled) return;
emit("click", event);
};
</script>

View File

@@ -26,13 +26,12 @@
<o-field v-if="hasInput">
<o-input
v-model="prompt"
expanded
class="input"
ref="input"
:class="{ 'is-danger': validationMessage }"
v-bind="inputAttrs"
@keydown.enter="confirm"
/>
<p class="help is-danger">{{ validationMessage }}</p>
</o-field>
</div>
</section>
@@ -83,15 +82,14 @@ const emit = defineEmits(["confirm", "cancel", "close"]);
const { t } = useI18n({ useScope: "global" });
const modalOpened = ref(false);
const validationMessage = ref("");
// const modalOpened = ref(false);
const prompt = ref<string>(props.hasInput ? props.inputAttrs?.value ?? "" : "");
const input = ref();
const dialogClass = computed(() => {
return [props.size];
});
// const dialogClass = computed(() => {
// return [props.size];
// });
/**
* Icon name (MDI) based on the type.
*/
@@ -114,10 +112,11 @@ const iconByType = computed(() => {
* Call the onConfirm prop (function) and close the Dialog.
*/
const confirm = () => {
console.log("dialog confirmed", input.value.$el);
if (input.value !== undefined) {
if (!input.value.checkValidity()) {
validationMessage.value = input.value.validationMessage;
nextTick(() => input.value.select());
const inputElement = input.value.$el.querySelector("input");
if (!inputElement.checkValidity()) {
nextTick(() => inputElement.select());
return;
}
}

View File

@@ -54,7 +54,9 @@ const props = withDefaults(
indefinite?: boolean;
}>(),
{
onAction: () => {},
onAction: () => {
// do nothing
},
cancelText: null,
variant: "dark",
position: "bottom-right",

View File

@@ -13,14 +13,14 @@ import {
MAPS_TILES,
RESOURCE_PROVIDERS,
RESTRICTIONS,
ROUTING_TYPE,
SEARCH_CONFIG,
TIMEZONES,
UPLOAD_LIMITS,
} from "@/graphql/config";
import { IAnonymousParticipationConfig, IConfig } from "@/types/config.model";
import { ApolloError } from "@apollo/client/core";
import { IConfig } from "@/types/config.model";
import { useQuery } from "@vue/apollo-composable";
import { computed, ref } from "vue";
import { computed } from "vue";
export function useTimezones() {
const {
@@ -36,26 +36,19 @@ export function useTimezones() {
}
export function useAnonymousParticipationConfig() {
const anonymousParticipationConfig = ref<
IAnonymousParticipationConfig | undefined
>(undefined);
const {
result: configResult,
error,
loading,
} = useQuery<{
config: Pick<IConfig, "anonymous">;
}>(ANONYMOUS_PARTICIPATION_CONFIG);
const error = ref<ApolloError | null>(null);
const anonymousParticipationConfig = computed(
() => configResult.value?.config?.anonymous?.participation
);
if (!anonymousParticipationConfig.value) {
const { onError, onResult } = useQuery<{
config: Pick<IConfig, "anonymous">;
}>(ANONYMOUS_PARTICIPATION_CONFIG);
onResult(({ data }) => {
anonymousParticipationConfig.value =
data.config?.anonymous?.participation;
});
onError((err) => (error.value = err));
}
return { anonymousParticipationConfig, error };
return { anonymousParticipationConfig, error, loading };
}
export function useAnonymousReportsConfig() {
@@ -147,6 +140,15 @@ export function useMapTiles() {
return { tiles, error, loading };
}
export function useRoutingType() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "maps">;
}>(ROUTING_TYPE);
const routingType = computed(() => result.value?.config.maps.routing.type);
return { routingType, error, loading };
}
export function useFeatures() {
const { result, error, loading } = useQuery<{
config: Pick<IConfig, "features">;

View File

@@ -55,7 +55,7 @@ export function useGroup(
name: unref(name),
...options,
}),
() => ({ enabled: unref(name) !== undefined })
() => ({ enabled: unref(name) !== undefined && unref(name) !== "" })
);
const group = computed(() => result.value?.group);
return { group, error, loading, onResult, onError, refetch };

View File

@@ -1,4 +1,3 @@
import { computed } from "vue";
import { useExportFormats, useUploadLimits } from "./apollo/config";
export const useHost = (): string => {

View File

@@ -347,6 +347,18 @@ export const MAPS_TILES = gql`
}
`;
export const ROUTING_TYPE = gql`
query RoutingType {
config {
maps {
routing {
type
}
}
}
}
`;
export const FEATURES = gql`
query Features {
config {

View File

@@ -19,6 +19,7 @@ export class Dialog {
onConfirm,
onCancel,
inputAttrs,
hasInput,
}: {
title?: string;
message: string;
@@ -29,7 +30,8 @@ export class Dialog {
size?: string;
onConfirm?: (prompt: string) => void;
onCancel?: (source: string) => void;
inputAttrs: Record<string, any>;
inputAttrs?: Record<string, any>;
hasInput?: boolean;
}) {
this.app.config.globalProperties.$oruga.modal.open({
component: DialogComponent,
@@ -44,6 +46,7 @@ export class Dialog {
onConfirm,
onCancel,
inputAttrs,
hasInput,
},
});
}

View File

@@ -68,7 +68,7 @@ export const routes = [
{
path: "/about",
name: RouteName.ABOUT,
component: (): Promise<any> => import("@/views/About.vue"),
component: (): Promise<any> => import("@/views/AboutView.vue"),
meta: { requiredAuth: false },
redirect: { name: RouteName.ABOUT_INSTANCE },
children: [

View File

@@ -1,36 +1,53 @@
<template>
<div class="p-6">
<div>
<header class="">
<h2 class="">{{ $t("Pick an identity") }}</h2>
<h2 class="">{{ t("Pick an identity") }}</h2>
</header>
<section class="">
<div class="list is-hoverable list-none">
<a
class="my-2 block dark:bg-violet-3 rounded-xl p-2"
<transition-group
tag="ul"
class="grid grid-cols-1 gap-y-3 m-5 max-w-md"
enter-active-class="duration-300 ease-out"
enter-from-class="transform opacity-0"
enter-to-class="opacity-100"
leave-active-class="duration-200 ease-in"
leave-from-class="opacity-100"
leave-to-class="transform opacity-0"
>
<li
class="relative focus-within:shadow-lg"
v-for="identity in identities"
:key="identity.id"
:class="{
active: currentIdentity && identity.id === currentIdentity.id,
}"
@click="currentIdentity = identity"
:key="identity?.id"
>
<div class="flex gap-2">
<img
class="rounded"
v-if="identity.avatar"
:src="identity.avatar.url"
alt=""
width="48"
height="48"
/>
<input
class="sr-only peer"
type="radio"
:value="identity"
name="availableActors"
v-model="currentIdentity"
:id="`availableActor-${identity?.id}`"
/>
<label
class="flex flex-wrap p-3 bg-white hover:bg-gray-50 dark:bg-violet-3 dark:hover:bg-violet-3/60 border border-gray-300 rounded-lg cursor-pointer peer-checked:ring-primary peer-checked:ring-2 peer-checked:border-transparent"
:for="`availableActor-${identity?.id}`"
>
<figure class="h-12 w-12" v-if="identity?.avatar">
<img
class="rounded-full h-full w-full object-cover"
:src="identity.avatar.url"
alt=""
width="48"
height="48"
/>
</figure>
<AccountCircle v-else :size="48" />
<div class="">
<p>@{{ identity.preferredUsername }}</p>
<small>{{ identity.name }}</small>
<div class="flex-1">
<h3>{{ identity?.name }}</h3>
<small>{{ `@${identity?.preferredUsername}` }}</small>
</div>
</div>
</a>
</div>
</label>
</li>
</transition-group>
</section>
<slot name="footer" />
</div>

View File

@@ -1,4 +1,3 @@
import { useI18n } from "vue-i18n";
<template>
<div>
<div

View File

@@ -321,7 +321,7 @@ import {
import { Dialog } from "@/plugins/dialog";
import { Notifier } from "@/plugins/notifier";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import Tag from "@/components/Tag.vue";
import Tag from "@/components/TagElement.vue";
const EVENTS_PER_PAGE = 10;
const POSTS_PER_PAGE = 10;

View File

@@ -308,7 +308,7 @@ import {
formatDateTimeString,
} from "@/filters/datetime";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import Tag from "@/components/Tag.vue";
import Tag from "@/components/TagElement.vue";
const EVENTS_PER_PAGE = 10;
const PARTICIPATIONS_PER_PAGE = 10;

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="user" class="section">
<div v-if="user">
<breadcrumbs-nav
:links="[
{ name: RouteName.ADMIN, text: t('Admin') },
@@ -18,10 +18,10 @@
<section>
<h2 class="text-lg font-bold mb-3">{{ t("Details") }}</h2>
<div class="flex flex-col">
<div class="overflow-x-auto sm:-mx-6">
<div class="overflow-x-auto">
<div class="inline-block py-2 min-w-full sm:px-2">
<div class="overflow-hidden shadow-md sm:rounded-lg">
<table v-if="metadata.length > 0" class="min-w-full">
<table v-if="metadata.length > 0" class="table w-full">
<tbody>
<tr
class="border-b"
@@ -229,48 +229,46 @@
aria-modal
>
<form @submit.prevent="updateUserRole">
<div>
<header>
<h2 class="modal-card-title">{{ t("Change user role") }}</h2>
</header>
<section>
<o-field>
<o-radio
v-model="newUser.role"
:native-value="ICurrentUserRole.ADMINISTRATOR"
>
{{ t("Administrator") }}
</o-radio>
</o-field>
<o-field>
<o-radio
v-model="newUser.role"
:native-value="ICurrentUserRole.MODERATOR"
>
{{ t("Moderator") }}
</o-radio>
</o-field>
<o-field>
<o-radio
v-model="newUser.role"
:native-value="ICurrentUserRole.USER"
>
{{ t("User") }}
</o-radio>
</o-field>
<o-checkbox v-model="newUser.notify">{{
t("Notify the user of the change")
}}</o-checkbox>
</section>
<footer class="mt-2 flex gap-2">
<o-button @click="isRoleChangeModalActive = false">{{
t("Close")
}}</o-button>
<o-button native-type="submit" variant="primary">{{
t("Change role")
}}</o-button>
</footer>
</div>
<header>
<h2>{{ t("Change user role") }}</h2>
</header>
<section>
<o-field>
<o-radio
v-model="newUser.role"
:native-value="ICurrentUserRole.ADMINISTRATOR"
>
{{ t("Administrator") }}
</o-radio>
</o-field>
<o-field>
<o-radio
v-model="newUser.role"
:native-value="ICurrentUserRole.MODERATOR"
>
{{ t("Moderator") }}
</o-radio>
</o-field>
<o-field>
<o-radio
v-model="newUser.role"
:native-value="ICurrentUserRole.USER"
>
{{ t("User") }}
</o-radio>
</o-field>
<o-checkbox v-model="newUser.notify">{{
t("Notify the user of the change")
}}</o-checkbox>
</section>
<footer class="mt-2 flex gap-2">
<o-button @click="isRoleChangeModalActive = false" outlined>{{
t("Close")
}}</o-button>
<o-button native-type="submit" variant="primary">{{
t("Change role")
}}</o-button>
</footer>
</form>
</o-modal>
<o-modal
@@ -284,24 +282,22 @@
aria-modal
>
<form @submit.prevent="confirmUser">
<div>
<header>
<h2>{{ t("Confirm user") }}</h2>
</header>
<section>
<o-checkbox v-model="newUser.notify">{{
t("Notify the user of the change")
}}</o-checkbox>
</section>
<footer>
<o-button @click="isConfirmationModalActive = false">{{
t("Close")
}}</o-button>
<o-button native-type="submit" variant="primary">{{
t("Confirm user")
}}</o-button>
</footer>
</div>
<header>
<h2>{{ t("Confirm user") }}</h2>
</header>
<section>
<o-checkbox v-model="newUser.notify">{{
t("Notify the user of the change")
}}</o-checkbox>
</section>
<footer>
<o-button @click="isConfirmationModalActive = false">{{
t("Close")
}}</o-button>
<o-button native-type="submit" variant="primary">{{
t("Confirm user")
}}</o-button>
</footer>
</form>
</o-modal>
</div>
@@ -424,7 +420,7 @@ const metadata = computed(
},
{
key: t("Uploaded media total size"),
value: formatBytes(user.value.mediaSize, 2, t("0 Bytes")),
value: formatBytes(user.value.mediaSize),
},
];
}

View File

@@ -443,10 +443,10 @@ const instanceLanguages = computed({
return codeForLanguage(language);
})
.filter((code) => code !== undefined) as string[];
// adminSettings = {
// ...adminSettings,
// instanceLanguages: newInstanceLanguages,
// };
settingsToWrite.value = {
...settingsToWrite.value,
instanceLanguages: newFilteredInstanceLanguages,
};
},
});

View File

@@ -218,10 +218,7 @@
</p>
<o-dropdown>
<template #trigger>
<o-button
icon-right="dots-horizontal"
#default="{ active }"
>
<o-button icon-right="dots-horizontal">
{{ t("Actions") }}
</o-button>
</template>
@@ -260,8 +257,8 @@
<o-dropdown-item
aria-role="listitem"
v-if="canManageEvent || event?.draft"
@click="openDeleteEventModalWrapper"
@keyup.enter="openDeleteEventModalWrapper"
@click="openDeleteEventModal"
@keyup.enter="openDeleteEventModal"
><span class="flex gap-1">
<Delete />
{{ t("Delete") }}
@@ -386,7 +383,11 @@
has-modal-card
ref="shareModal"
>
<share-event-modal :event="event" :eventCapacityOK="eventCapacityOK" />
<share-event-modal
v-if="event"
:event="event"
:eventCapacityOK="eventCapacityOK"
/>
</o-modal>
<o-modal
v-model:active="isJoinModalActive"
@@ -394,34 +395,34 @@
ref="participationModal"
:close-button-aria-label="t('Close')"
>
<identity-picker v-model="identity">
<identity-picker v-if="identity" v-model="identity">
<template #footer>
<footer class="modal-card-foot">
<button
class="button"
<footer class="flex gap-2">
<o-button
outlined
ref="cancelButton"
@click="isJoinModalActive = false"
@keyup.enter="isJoinModalActive = false"
>
{{ t("Cancel") }}
</button>
<button
</o-button>
<o-button
v-if="identity"
class="button is-primary"
variant="primary"
ref="confirmButton"
@click="
event?.joinOptions === EventJoinOptions.RESTRICTED
? joinEventWithConfirmation(identity)
: joinEvent(identity)
? joinEventWithConfirmation(identity as IPerson)
: joinEvent(identity as IPerson)
"
@keyup.enter="
event?.joinOptions === EventJoinOptions.RESTRICTED
? joinEventWithConfirmation(identity)
: joinEvent(identity)
? joinEventWithConfirmation(identity as IPerson)
: joinEvent(identity as IPerson)
"
>
{{ t("Confirm my particpation") }}
</button>
</o-button>
</footer>
</template>
</identity-picker>
@@ -449,7 +450,10 @@
</p>
<form
@submit.prevent="
joinEvent(actorForConfirmation, messageForConfirmation)
joinEvent(
actorForConfirmation as IPerson,
messageForConfirmation
)
"
>
<o-field :label="t('Message')">
@@ -487,22 +491,13 @@
:can-cancel="['escape', 'outside']"
>
<template #default="props">
<!-- <event-map
:routingType="routingType"
<event-map
:routingType="routingType ?? RoutingType.OPENSTREETMAP"
:address="event.physicalAddress"
@close="props.close"
/> -->
/>
</template>
</o-modal>
<dialog
variant="danger"
:title="t('Delete event')"
:message="deleteEventMessage"
:confirmText="t('Delete {eventTitle}', { eventTitle: event?.title })"
:onConfirm="
() => (event?.id ? deleteEvent({ eventId: event?.id }) : null)
"
></dialog>
</div>
</div>
</template>
@@ -514,12 +509,14 @@ import {
EventVisibility,
MemberRole,
ParticipantRole,
RoutingType,
} from "@/types/enums";
import {
EVENT_PERSON_PARTICIPATION,
EVENT_PERSON_PARTICIPATION_SUBSCRIPTION_CHANGED,
// EVENT_PERSON_PARTICIPATION_SUBSCRIPTION_CHANGED,
FETCH_EVENT,
JOIN_EVENT,
LEAVE_EVENT,
} from "@/graphql/event";
import { IEvent } from "@/types/event.model";
import {
@@ -543,7 +540,7 @@ import {
isParticipatingInThisEvent,
removeAnonymousParticipation,
} from "@/services/AnonymousParticipationStorage";
import Tag from "@/components/Tag.vue";
import Tag from "@/components/TagElement.vue";
import EventMetadataSidebar from "@/components/Event/EventMetadataSidebar.vue";
import EventBanner from "@/components/Event/EventBanner.vue";
import EventMap from "@/components/Event/EventMap.vue";
@@ -583,19 +580,21 @@ import {
useAnonymousParticipationConfig,
useAnonymousReportsConfig,
useEventCategories,
useRoutingType,
} from "@/composition/apollo/config";
import { useCreateReport } from "@/composition/apollo/report";
import Share from "vue-material-design-icons/Share.vue";
import { useI18n } from "vue-i18n";
import { useProgrammatic } from "@oruga-ui/oruga-next";
import { Dialog } from "@/plugins/dialog";
import { Notifier } from "@/plugins/notifier";
import { AbsintheGraphQLErrors } from "@/types/errors.model";
import { useHead } from "@vueuse/head";
import { Snackbar } from "@/plugins/snackbar";
const ShareEventModal = defineAsyncComponent(
() => import("@/components/Event/ShareEventModal.vue")
);
/* eslint-disable @typescript-eslint/no-unused-vars */
const IntegrationTwitch = defineAsyncComponent(
() => import("@/components/Event/Integrations/TwitchIntegration.vue")
);
@@ -611,6 +610,7 @@ const IntegrationJitsiMeet = defineAsyncComponent(
const IntegrationEtherpad = defineAsyncComponent(
() => import("@/components/Event/Integrations/EtherpadIntegration.vue")
);
/* eslint-enable @typescript-eslint/no-unused-vars */
const props = defineProps<{
uuid: string;
@@ -629,7 +629,7 @@ const currentActorId = computed(() => currentActor.value?.id);
const { loggedUser } = useLoggedUser();
const {
result: participationsResult,
subscribeToMore: subscribeToMoreParticipation,
// subscribeToMore: subscribeToMoreParticipation,
} = useQuery<{ person: IPerson }>(
EVENT_PERSON_PARTICIPATION,
() => ({
@@ -653,7 +653,7 @@ const {
// }));
const participations = computed(
() => participationsResult.value?.person.participations.elements ?? []
() => participationsResult.value?.person.participations?.elements ?? []
);
const { person } = usePersonStatusGroup(
@@ -680,7 +680,7 @@ const { identities } = useCurrentUserIdentities();
// };
// },
const identity = ref<IPerson | null>(null);
const identity = ref<IPerson | undefined | null>(null);
const reportModal = ref();
const oldParticipationRole = ref<string | undefined>(undefined);
@@ -786,12 +786,6 @@ onMounted(async () => {
// return router.push({ name: RouteName.HOME });
// });
});
/**
* Delete the event, then redirect to home.
*/
const openDeleteEventModalWrapper = async (): Promise<void> => {
await openDeleteEventModal(event.value);
};
const deleteEventMessage = computed(() => {
const participantsLength = event.value?.participantStats.participant;
@@ -814,17 +808,62 @@ const deleteEventMessage = computed(() => {
})}`;
});
const { mutate: deleteEvent } = useDeleteEvent();
const notifier = inject<Notifier>("notifier");
const dialog = inject<Dialog>("dialog");
const openDeleteEventModal = async (
event: IEvent | undefined
): Promise<void> => {
function escapeRegExp(string: string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
const {
mutate: deleteEvent,
onDone: onDeleteEventDone,
onError: onDeleteEventError,
} = useDeleteEvent();
const escapeRegExp = (string: string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
};
const notifier = inject<Notifier>("notifier");
const openDeleteEventModal = () => {
dialog?.prompt({
title: t("Delete event"),
message: deleteEventMessage.value,
confirmText: t("Delete event"),
cancelText: t("Cancel"),
variant: "danger",
hasIcon: true,
hasInput: true,
inputAttrs: {
placeholder: event.value?.title,
pattern: escapeRegExp(event.value?.title ?? ""),
},
onConfirm: (result: string) => {
console.log("calling delete event", result);
if (result.trim() === event.value?.title) {
event.value?.id ? deleteEvent({ eventId: event.value?.id }) : null;
}
},
});
};
onDeleteEventDone(() => {
router.push({ name: RouteName.MY_EVENTS });
});
onDeleteEventError((error) => {
console.error(error);
});
const {
mutate: createReportMutation,
onDone: onCreateReportDone,
onError: onCreateReportError,
} = useCreateReport();
onCreateReportDone(() => {
notifier?.success(t("Event {eventTitle} reported", { eventTitle }));
});
onCreateReportError((error) => {
console.error(error);
});
const reportEvent = async (
content: string,
@@ -836,22 +875,12 @@ const reportEvent = async (
reportModal.value.close();
if (!organizer.value) return;
const { mutate, onDone, onError } = useCreateReport();
mutate({
createReportMutation({
eventId: event.value?.id ?? "",
reportedId: organizer.value?.id ?? "",
content,
forward,
});
onDone(() => {
notifier?.success(t("Event {eventTitle} reported", { eventTitle }));
});
onError((error) => {
console.error(error);
});
};
const joinEventWithConfirmation = (actor: IPerson): void => {
@@ -876,7 +905,7 @@ const {
const participationCachedData = store.readQuery<{ person: IPerson }>({
query: EVENT_PERSON_PARTICIPATION,
variables: { eventId: event.value?.id, actorId: identity.value.id },
variables: { eventId: event.value?.id, actorId: identity.value?.id },
});
if (participationCachedData?.person == undefined) {
@@ -887,7 +916,7 @@ const {
}
store.writeQuery({
query: EVENT_PERSON_PARTICIPATION,
variables: { eventId: event.value?.id, actorId: identity.value.id },
variables: { eventId: event.value?.id, actorId: identity.value?.id },
data: {
person: {
...participationCachedData?.person,
@@ -934,14 +963,14 @@ const {
}));
const joinEvent = async (
identity: IPerson,
identityForJoin: IPerson,
message: string | null = null
): Promise<void> => {
isJoinConfirmationModalActive.value = false;
isJoinModalActive.value = false;
joinEventMutation({
eventId: event.value?.id,
actorId: identity?.id,
actorId: identityForJoin?.id,
message,
});
};
@@ -960,8 +989,6 @@ onJoinEventMutationError((error) => {
console.error(error);
});
const dialog = inject<Dialog>("dialog");
const confirmLeave = (): void => {
dialog?.confirm({
title: t('Leaving event "{title}"', {
@@ -975,10 +1002,11 @@ const confirmLeave = (): void => {
),
confirmText: t("Leave event"),
cancelText: t("Cancel"),
type: "danger",
variant: "danger",
hasIcon: true,
onConfirm: () => {
if (currentActor.value?.id) {
if (event.value && currentActor.value?.id) {
console.log("calling leave event");
leaveEvent(event.value, currentActor.value.id);
}
},
@@ -1071,14 +1099,14 @@ onFetchEventError(({ graphQLErrors }) =>
handleErrors(graphQLErrors as AbsintheGraphQLErrors)
);
const actorIsParticipant = computed((): boolean => {
if (actorIsOrganizer.value) return true;
// const actorIsParticipant = computed((): boolean => {
// if (actorIsOrganizer.value) return true;
return (
participations.value.length > 0 &&
participations.value[0].role === ParticipantRole.PARTICIPANT
);
});
// return (
// participations.value.length > 0 &&
// participations.value[0].role === ParticipantRole.PARTICIPANT
// );
// });
const actorIsOrganizer = computed((): boolean => {
return (
@@ -1101,11 +1129,11 @@ const canManageEvent = computed((): boolean => {
return actorIsOrganizer.value || hasGroupPrivileges.value;
});
const endDate = computed((): string | undefined => {
return event.value?.endsOn && event.value.endsOn > event.value.beginsOn
? event.value.endsOn
: event.value?.beginsOn;
});
// const endDate = computed((): string | undefined => {
// return event.value?.endsOn && event.value.endsOn > event.value.beginsOn
// ? event.value.endsOn
// : event.value?.beginsOn;
// });
const maximumAttendeeCapacity = computed((): number | undefined => {
return event.value?.options?.maximumAttendeeCapacity;
@@ -1122,21 +1150,22 @@ const eventCapacityOK = computed((): boolean => {
);
});
const numberOfPlacesStillAvailable = computed((): number | undefined => {
if (event.value?.draft) return maximumAttendeeCapacity.value;
return (
(maximumAttendeeCapacity.value ?? 0) -
(event.value?.participantStats.participant ?? 0)
);
});
// const numberOfPlacesStillAvailable = computed((): number | undefined => {
// if (event.value?.draft) return maximumAttendeeCapacity.value;
// return (
// (maximumAttendeeCapacity.value ?? 0) -
// (event.value?.participantStats.participant ?? 0)
// );
// });
const anonymousParticipationConfirmed = async (): Promise<boolean> => {
return isParticipatingInThisEvent(props.uuid);
};
const cancelAnonymousParticipation = async (): Promise<void> => {
if (!event.value || !anonymousActorId.value) return;
const token = (await getLeaveTokenForParticipation(props.uuid)) as string;
await leaveEvent(event.value, anonymousActorId.value, token);
leaveEvent(event.value, anonymousActorId.value, token);
await removeAnonymousParticipation(props.uuid);
anonymousParticipation.value = null;
};
@@ -1192,19 +1221,134 @@ const integrations = computed((): Record<string, IEventMetadataDescription> => {
const showMap = ref(false);
const routingType = computed((): string | undefined => {
return config.value?.maps?.routing?.type;
});
const { routingType } = useRoutingType();
const eventCategory = computed((): string | undefined => {
if (event.value?.category === "MEETING") {
return undefined;
}
return (eventCategories.value ?? []).find((eventCategory) => {
return eventCategory.id === event.value?.category;
return (eventCategories.value ?? []).find((eventCategoryToFind) => {
return eventCategoryToFind.id === event.value?.category;
})?.label as string;
});
const {
mutate: leaveEventMutation,
onDone: onLeaveEventMutationDone,
onError: onLeaveEventMutationError,
} = useMutation<{ leaveEvent: { actor: { id: string } } }>(LEAVE_EVENT, () => ({
update: (
store: ApolloCache<{
leaveEvent: IParticipant;
}>,
{ data }: FetchResult,
{ context, variables }
) => {
if (data == null) return;
let participation;
const token = context?.token;
const actorId = variables?.actorId;
const localEventId = variables?.eventId;
const eventUUID = context?.eventUUID;
const isAnonymousParticipationConfirmed =
context?.isAnonymousParticipationConfirmed;
if (!token) {
const participationCachedData = store.readQuery<{
person: IPerson;
}>({
query: EVENT_PERSON_PARTICIPATION,
variables: { eventId: localEventId, actorId },
});
if (participationCachedData == null) return;
const { person: cachedPerson } = participationCachedData;
[participation] = cachedPerson?.participations?.elements ?? [undefined];
store.modify({
id: `Person:${actorId}`,
fields: {
participations() {
return {
elements: [],
total: 0,
};
},
},
});
}
const eventCachedData = store.readQuery<{ event: IEvent }>({
query: FETCH_EVENT,
variables: { uuid: eventUUID },
});
if (eventCachedData == null) return;
const { event: eventCached } = eventCachedData;
if (eventCached === null) {
console.error("Cannot update event cache, because of null value.");
return;
}
const participantStats = { ...eventCached.participantStats };
if (participation && participation?.role === ParticipantRole.NOT_APPROVED) {
participantStats.notApproved -= 1;
} else if (isAnonymousParticipationConfirmed === false) {
participantStats.notConfirmed -= 1;
} else {
participantStats.going -= 1;
participantStats.participant -= 1;
}
store.writeQuery({
query: FETCH_EVENT,
variables: { uuid: eventUUID },
data: {
event: {
...eventCached,
participantStats,
},
},
});
},
}));
const leaveEvent = (
eventToLeave: IEvent,
actorId: string,
token: string | null = null,
isAnonymousParticipationConfirmed: boolean | null = null
): void => {
leaveEventMutation(
{
eventId: eventToLeave.id,
actorId,
token,
},
{
context: {
token,
isAnonymousParticipationConfirmed,
eventUUID: eventToLeave.uuid,
},
}
);
};
onLeaveEventMutationDone(({ data }) => {
if (data) {
notifier?.success(t("You have cancelled your participation"));
}
});
const snackbar = inject<Snackbar>("snackbar");
onLeaveEventMutationError((error) => {
snackbar?.open({
message: error.message,
variant: "danger",
position: "bottom",
});
console.error(error);
});
useHead({
title: computed(() => eventTitle.value ?? ""),
meta: [{ name: "description", content: eventDescription.value }],

View File

@@ -97,7 +97,6 @@
<event-participation-card
v-if="'role' in element"
:participation="element"
:options="{ hideDate: false }"
@event-deleted="eventDeleted"
class="participation"
/>

View File

@@ -283,7 +283,7 @@ import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import Incognito from "vue-material-design-icons/Incognito.vue";
import EmptyContent from "@/components/Utils/EmptyContent.vue";
import { Notifier } from "@/plugins/notifier";
import Tag from "@/components/Tag.vue";
import Tag from "@/components/TagElement.vue";
const PARTICIPANTS_PER_PAGE = 10;
const MESSAGE_ELLIPSIS_LENGTH = 130;

View File

@@ -267,7 +267,7 @@ import {
import { formatTimeString, formatDateString } from "@/filters/datetime";
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
import { Notifier } from "@/plugins/notifier";
import Tag from "@/components/Tag.vue";
import Tag from "@/components/TagElement.vue";
const { t } = useI18n({ useScope: "global" });

View File

@@ -135,7 +135,7 @@ import { IGroup, usernameWithDomain, displayName } from "@/types/actor";
import { ActivityType } from "@/types/enums";
import { Paginate } from "@/types/paginate";
import { IActivity } from "../../types/activity.model";
import Observer from "../../components/Utils/Observer.vue";
import Observer from "../../components/Utils/ObserverElement.vue";
import SkeletonActivityItem from "../../components/Activity/SkeletonActivityItem.vue";
import RouteName from "../../router/name";
import TimelineText from "vue-material-design-icons/TimelineText.vue";

View File

@@ -219,7 +219,7 @@ import {
displayName,
} from "@/types/actor";
import RouteName from "@/router/name";
import Tag from "@/components/Tag.vue";
import Tag from "@/components/TagElement.vue";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import ActorInline from "@/components/Account/ActorInline.vue";
import { formatDistanceToNowStrict, Locale } from "date-fns";

View File

@@ -202,7 +202,6 @@ import {
InternalRefetchQueriesInclude,
} from "@apollo/client/core";
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";
@@ -275,7 +274,7 @@ const moveModal = ref(false);
const renameModal = ref(false);
const modalError = ref("");
const resourceRenameInput = ref<HTMLElement>();
const resourceRenameInput = ref<any>();
const modalNewResourceInput = ref<HTMLElement>();
const modalNewResourceLinkInput = ref<HTMLElement>();
@@ -307,7 +306,7 @@ const {
refetchQueries: () => postRefreshQueries(),
}));
createResourceDone(({ data }) => {
createResourceDone(() => {
createLinkResourceModal.value = false;
createResourceModal.value = false;
newResource.title = "";
@@ -447,25 +446,25 @@ const { mutate: deleteResource, onError: onDeleteResourceError } = useMutation(
onDeleteResourceError((e) => console.error(e));
const handleRename = async (resource: IResource): Promise<void> => {
const handleRename = async (resourceToRename: IResource): Promise<void> => {
renameModal.value = true;
updatedResource.value = { ...resource };
updatedResource.value = { ...resourceToRename };
await nextTick();
resourceRenameInput.value?.$el.focus();
resourceRenameInput.value?.$el.querySelector("input")?.select();
};
const handleMove = (resource: IResource): void => {
const handleMove = (resourceToMove: IResource): void => {
moveModal.value = true;
updatedResource.value = { ...resource };
updatedResource.value = { ...resourceToMove };
};
const moveResource = async (
resource: IResource,
resourceToMove: IResource,
oldParent: IResource | undefined
): Promise<void> => {
const parentPath = oldParent && oldParent.path ? oldParent.path || "/" : "/";
await updateResource(resource, parentPath);
await updateResource(resourceToMove, parentPath);
moveModal.value = false;
};
@@ -501,10 +500,10 @@ const { mutate: updateResourceMutation } = useMutation<{
console.error("Cannot update resource cache, because of null value.");
return;
}
const updatedResource: IResource = data.updateResource;
const postUpdatedResource: IResource = data.updateResource;
const updatedElementList = oldParentCachedResource.children.elements.filter(
(cachedResource) => cachedResource.id !== updatedResource.id
(cachedResource) => cachedResource.id !== postUpdatedResource.id
);
store.writeQuery({
@@ -526,14 +525,14 @@ const { mutate: updateResourceMutation } = useMutation<{
console.debug("Finished removing ressource from old parent");
console.debug("Adding resource to new parent");
if (!updatedResource.parent || !updatedResource.parent.path) {
if (!postUpdatedResource.parent || !postUpdatedResource.parent.path) {
console.debug("No cache found for new parent");
return;
}
const newParentCachedData = store.readQuery<{ resource: IResource }>({
query: GET_RESOURCE,
variables: {
path: updatedResource.parent.path,
path: postUpdatedResource.parent.path,
username: resource.value.actor.preferredUsername,
},
});
@@ -547,7 +546,7 @@ const { mutate: updateResourceMutation } = useMutation<{
store.writeQuery({
query: GET_RESOURCE,
variables: {
path: updatedResource.parent.path,
path: postUpdatedResource.parent.path,
username: resource.value.actor.preferredUsername,
},
data: {
@@ -565,15 +564,15 @@ const { mutate: updateResourceMutation } = useMutation<{
}));
const updateResource = async (
resource: IResource,
resourceToUpdate: IResource,
parentPath: string | null = null
): Promise<void> => {
updateResourceMutation(
{
id: resource.id,
title: resource.title,
parentId: resource.parent ? resource.parent.id : null,
path: resource.path,
id: resourceToUpdate.id,
title: resourceToUpdate.title,
parentId: resourceToUpdate.parent ? resourceToUpdate.parent.id : null,
path: resourceToUpdate.path,
},
{ context: { parentPath } }
);