eventcally/project/templates/layout_vue.html
2023-03-30 23:47:31 +02:00

476 lines
17 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "layout.html" %}
{% block header_before_site_js %}
<link
type="text/css"
rel="stylesheet"
href="{{ url_for('static', filename='ext/bootstrap-vue.2.21.2.min.css')}}"
/>
{% if False | env_override('FLASK_DEBUG') %}
<script src="{{ url_for('static', filename='ext/vue.2.6.14.js')}}"></script>
{% else %}
<script src="{{ url_for('static', filename='ext/vue.2.6.14.min.js')}}"></script>
{% endif %}
<script src="{{ url_for('static', filename='ext/bootstrap-vue.2.21.2.min.js')}}"></script>
<script src="{{ url_for('static', filename='ext/vue-router.2.0.0.min.js')}}"></script>
<script src="{{ url_for('static', filename='ext/vue-i18n.8.25.0.min.js')}}"></script>
<script src="{{ url_for('static', filename='ext/axios.0.21.1.min.js')}}"></script>
<script src="{{ url_for('static', filename='ext/lodash.4.17.21.min.js')}}"></script>
<script src="{{ url_for('static', filename='ext/portal-vue.2.1.7.umd.min.js')}}"></script>
<link
href="{{ url_for('static', filename='ext/VueTypeaheadBootstrap.2.12.0.css')}}"
rel="stylesheet"
/>
<script src="{{ url_for('static', filename='ext/VueTypeaheadBootstrap.2.12.0.umd.min.js')}}"></script>
<script src="{{ url_for('static', filename='ext/vee-validate.full.3.4.11.min.js')}}"></script>
<script src="{{ url_for('static', filename='vue/common/typeahead.vue.js')}}"></script>
<script src="{{ url_for('static', filename='vue/common/validated-input.vue.js')}}"></script>
<script src="{{ url_for('static', filename='vue/common/validated-switch.vue.js')}}"></script>
<script src="{{ url_for('static', filename='vue/common/validated-textarea.vue.js')}}"></script>
<script src="{{ url_for('static', filename='vue/common/event-warning-pills.vue.js')}}"></script>
{% block component_scripts %}
{% endblock %}
{% endblock %}
{% block content %}
{% block vue_container %}
<div id="vue-container"{% block vue_container_attribs %}{% endblock vue_container_attribs %}><router-view></router-view></div>
{% endblock %}
<script>
Vue.component("vue-typeahead-bootstrap", VueTypeaheadBootstrap);
Vue.component("ValidationObserver", VeeValidate.ValidationObserver);
Vue.component("ValidationProvider", VeeValidate.ValidationProvider);
Vue.component("custom-typeahead", CustomTypeahead);
Vue.component("validated-input", ValidatedInput);
Vue.component("validated-switch", ValidatedSwitch);
Vue.component("validated-textarea", ValidatedTextarea);
Vue.component("event-warning-pills", EventWarningPills);
{% block component_definitions %}
{% endblock %}
const sharedMessages = {
en: {
shared: {
models: {
adminUnit: {
className: "Organization",
listName: "Organizations",
name: "Name",
shortName: "Short name",
},
adminUnitRelation: {
targetOrganization: "Other organization",
autoVerifyEventReferenceRequests: "Verify reference requests automatically",
autoVerifyEventReferenceRequestsDescription: "If set, all upcoming reference requests of the other organization are verified automatically.",
verify: "Verify other organization",
verifyDescription: "If set, events of the other organization are publicly visible.",
},
adminUnitInvitation: {
email: "Email",
emailDescription: "The invitation will be sent to this email address.",
organizationName: "New organization's name",
relationAutoVerifyEventReferenceRequests: "Verify reference requests automatically",
relationAutoVerifyEventReferenceRequestsDescription: "If set, all upcoming reference requests of the new organization are verified automatically.",
relationVerify: "Verify new organization",
relationVerifyDescription: "If set, events of the new organization are publicly visible.",
},
customWidget: {
className: "Custom widget",
listName: "Custom widgets",
widgetType: "Type",
widgetTypeSearch: "Search",
widgetTypeCalendar: "Calendar",
name: "Name",
},
event: {
className: "Event",
listName: "Events",
name: "Name",
},
eventList: {
className: "Event list",
listName: "Event lists",
name: "Name",
},
eventReport: {
contactName: "Name",
contactEmail: "Email",
contactNameDescription: "First and last name",
message: "Message",
messageDescription: "Briefly describe in your words why the event is objectionable.",
},
},
cancel: "Cancel",
close: "Close",
decline: "Decline",
save: "Save",
submit: "Submit",
view: "View",
edit: "Edit",
delete: "Delete",
remove: "Remove",
emptyData: "No data available",
errors: {
uniqueViolation:
"An entry with the entered values already exists. Duplicate entries are not allowed.",
unprocessableEntity:
"The request was well-formed but was unable to be followed due to semantic errors.",
},
toast: {
errorTitle: "Error",
successTitle: "Success",
},
autocomplete: {
instruction: "Type to search",
},
validation: {
alpha: "The {_field_} field may only contain alphabetic characters",
alpha_num:
"The {_field_} field may only contain alpha-numeric characters",
alpha_dash:
"The {_field_} field may contain alpha-numeric characters as well as dashes and underscores",
alpha_spaces:
"The {_field_} field may only contain alphabetic characters as well as spaces",
between: "The {_field_} field must be between {min} and {max}",
confirmed: "The {_field_} field confirmation does not match",
digits:
"The {_field_} field must be numeric and exactly contain {length} digits",
dimensions:
"The {_field_} field must be {width} pixels by {height} pixels",
email: "The {_field_} field must be a valid email",
excluded: "The {_field_} field is not a valid value",
ext: "The {_field_} field is not a valid file",
image: "The {_field_} field must be an image",
integer: "The {_field_} field must be an integer",
length: "The {_field_} field must be {length} long",
max_value: "The {_field_} field must be {max} or less",
max: "The {_field_} field may not be greater than {length} characters",
mimes: "The {_field_} field must have a valid file type",
min_value: "The {_field_} field must be {min} or more",
min: "The {_field_} field must be at least {length} characters",
numeric: "The {_field_} field may only contain numeric characters",
oneOf: "The {_field_} field is not a valid value",
regex: "The {_field_} field format is invalid",
required_if: "The {_field_} field is required",
required: "The {_field_} field is required",
size: "The {_field_} field size must be less than {size}KB",
double: "The {_field_} field must be a valid decimal",
uniqueOrganizationName: "Name is already taken",
url: "The {_field_} field must be a valid URL",
},
},
},
de: {
shared: {
models: {
adminUnit: {
className: "Organisation",
listName: "Organisationen",
name: "Name",
shortName: "Kurzname",
},
adminUnitRelation: {
targetOrganization: "Andere Organisation",
autoVerifyEventReferenceRequests:
"Empfehlungsanfragen automatisch verifizieren",
autoVerifyEventReferenceRequestsDescription: "Wenn gesetzt, werden alle zukünftigen Empfehlungsanfragen der anderen Organisation automatisch verifiziert.",
verify: "Andere Organisation verifizieren",
verifyDescription: "Wenn gesetzt, sind Veranstaltungen der anderen Organisation öffentlich sichtbar.",
},
adminUnitInvitation: {
email: "Email",
emailDescription: "An diese Email-Adresse wird die Einladung gesendet.",
organizationName: "Name der neuen Organisation",
relationAutoVerifyEventReferenceRequests: "Empfehlungsanfragen automatisch verifizieren",
relationAutoVerifyEventReferenceRequestsDescription: "Wenn gesetzt, werden alle zukünftigen Empfehlungsanfragen der neuen Organisation automatisch verifiziert.",
relationVerify: "Neue Organisation verifizieren",
relationVerifyDescription: "Wenn gesetzt, sind Veranstaltungen der neuen Organisation öffentlich sichtbar.",
},
customWidget: {
className: "Custom widget",
listName: "Custom widgets",
widgetTypeSearch: "Suche",
widgetTypeCalendar: "Kalender",
widgetType: "Typ",
name: "Name",
},
event: {
className: "Veranstaltung",
listName: "Veranstaltungen",
name: "Name",
},
eventList: {
className: "Veranstaltungsliste",
listName: "Veranstaltungslisten",
name: "Name",
},
eventReport: {
contactName: "Name",
contactNameDescription: "Vor- und Nachname",
contactEmail: "Email-Adresse",
message: "Mitteilung",
messageDescription: "Beschreibe kurz in deinen Worten, warum die Veranstaltung zu beanstanden ist.",
},
},
cancel: "Abbrechen",
close: "Schließen",
decline: "Ablehnen",
save: "Speichern",
submit: "Senden",
view: "Anzeigen",
edit: "Bearbeiten",
delete: "Löschen",
remove: "Entfernen",
emptyData: "Keine Daten vorhanden",
errors: {
uniqueViolation:
"Ein Eintrag mit den eingegebenen Werten existiert bereits. Doppelte Einträge sind nicht erlaubt.",
unprocessableEntity:
"Die Anfrage konnte aufgrund von semantischen Fehlern nicht beantwortet werden.",
},
toast: {
errorTitle: "Fehler",
successTitle: "Erfolg",
},
autocomplete: {
instruction: "Tippen um zu suchen",
},
validation: {
alpha: "{_field_} darf nur alphabetische Zeichen enthalten",
alpha_dash:
"{_field_} darf alphanumerische Zeichen sowie Striche und Unterstriche enthalten",
alpha_num: "{_field_} darf nur alphanumerische Zeichen enthalten",
alpha_spaces:
"{_field_} darf nur alphanumerische Zeichen und Leerzeichen enthalten",
between: "{_field_} muss zwischen {min} und {max} liegen",
confirmed: "Die Bestätigung von {_field_} stimmt nicht überein",
digits:
"{_field_} muss numerisch sein und exakt {length} Ziffern enthalten",
dimensions: "{_field_} muss {width} x {height} Bildpunkte groß sein",
email: "{_field_} muss eine gültige E-Mail-Adresse sein",
excluded: "{_field_} muss ein gültiger Wert sein",
ext: "{_field_} muss eine gültige Datei sein",
image: "{_field_} muss eine Grafik sein",
oneOf: "{_field_} muss ein gültiger Wert sein",
integer: "{_field_} muss eine ganze Zahl sein",
length: "Die Länge von {_field_} muss {length} sein",
max: "{_field_} darf nicht länger als {length} Zeichen sein",
max_value: "{_field_} darf maximal {max} sein",
mimes: "{_field_} muss einen gültigen Dateityp haben",
min: "{_field_} muss mindestens {length} Zeichen lang sein",
min_value: "{_field_} muss mindestens {min} sein",
numeric: "{_field_} darf nur numerische Zeichen enthalten",
regex: "Das Format von {_field_} ist ungültig",
required: "{_field_} ist ein Pflichtfeld",
required_if: "{_field_} ist ein Pflichtfeld",
size: "{_field_} muss kleiner als {size}KB sein",
double: "Das Feld {_field_} muss eine gültige Dezimalzahl sein",
uniqueOrganizationName: "Der Name ist bereits vergeben",
url: "Das Feld {_field_} muss eine gültige URL sein",
},
},
},
};
const i18n = new VueI18n({
locale: "de",
messages: sharedMessages,
silentFallbackWarn: true,
});
VeeValidate.configure({
defaultMessage: (field, values) => {
return i18n.t(`shared.validation.${values._rule_}`, values);
}
});
Object.keys(VeeValidate.Rules).forEach((rule) => {
VeeValidate.extend(rule, VeeValidate.Rules[rule]);
});
VeeValidate.extend('uniqueOrganizationName', {
validate: async value => {
try {
const response = await axios.get(
`/api/v1/organizations?keyword=${value}`, {
withCredentials: true,
}
);
return !response.data.items.some(o => o.name == value);
} catch (err) {
return true;
}
}
}, {
immediate: false
});
VeeValidate.extend('url', {
validate: value => {
if (value) {
return /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/.test(value);
}
return false;
}
});
{% block vue_routes %}
const routes = [];
{% endblock %}
const router = new VueRouter({
routes: routes,
mode: "history",
base: "/",
});
axios.defaults.baseURL = "{{ get_base_url() }}";
axios.interceptors.request.use(
function (config) {
if (config) {
this.app.handleAxiosStart(config);
}
return config;
},
function (error) {
this.app.handleAxiosError(error);
return Promise.reject(error);
}
);
axios.interceptors.response.use(
function (response) {
if (response && response.config) {
this.app.handleAxiosFinish(response.config);
}
return response;
},
function (error) {
this.app.handleAxiosError(error);
return Promise.reject(error);
}
);
{% block vue_app_data %}
var vue_app_data = {};
{% endblock %}
var app = new Vue({
el: "#vue-container",
i18n,
router: router,
data: vue_app_data,
methods: {
handleAxiosStart(config) {
if (
config &&
config.handler &&
config.handler.hasOwnProperty("handleRequestStart")
) {
config.handler.handleRequestStart();
}
if (
config &&
config.hasOwnProperty("handleLoading")
) {
config.handleLoading(true);
}
},
handleAxiosFinish(config) {
if (
config &&
config.handler &&
config.handler.hasOwnProperty("handleRequestFinish")
) {
config.handler.handleRequestFinish();
}
if (
config &&
config.hasOwnProperty("handleLoading")
) {
config.handleLoading(false);
}
},
handleAxiosError(error) {
if (error && error.config) {
this.handleAxiosFinish(error.config);
}
const status = error && error.response && error.response.status;
let message = error.message || error;
if (status == 400 || status == 422) {
message =
(error &&
error.response &&
error.response.data &&
error.response.data.message) ||
error;
errorName =
error &&
error.response &&
error.response.data &&
error.response.data.name;
if (errorName == "Unique Violation") {
message = this.$t("shared.errors.uniqueViolation");
}
else if (errorName == "Unprocessable Entity") {
message = this.$t("shared.errors.unprocessableEntity");
}
}
if (
error.config &&
error.config.handler &&
error.config.handler.hasOwnProperty("handleRequestError")
) {
error.config.handler.handleRequestError(error, message);
} else {
this.makeErrorToast(message);
}
},
makeErrorToast(message) {
this.makeToast(message, "danger", this.$t("shared.toast.errorTitle"));
},
makeSuccessToast(message) {
this.makeToast(
message,
"success",
this.$t("shared.toast.successTitle")
);
},
makeToast(message, variant, title) {
this.$bvToast.toast(message, {
title: title,
variant: variant,
toaster: "b-toaster-top-center",
noCloseButton: true,
solid: true,
});
},
goBack(fallbackPath) {
window.history.length > 1 ? this.$router.go(-1) : this.$router.push({ path: fallbackPath })
},
render_event_date_instance(value, allday, format = "dd. DD.MM.YYYY LT", alldayFormat = "dd. DD.MM.YYYY") {
const instance = moment(value);
if (allday) {
return instance.format(alldayFormat);
}
return instance.format(format);
},
url_for_image(image, size) {
return `${axios.defaults.baseURL}${image.image_url}?s=${size}`
},
},
});
</script>
{% endblock %}