Migrate to Vue 3 and Vite
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -10,13 +10,13 @@
|
||||
<router-link
|
||||
v-if="index === 0"
|
||||
:to="element"
|
||||
class="inline-flex items-center text-gray-800 hover:text-gray-900"
|
||||
class="inline-flex items-center text-gray-800 hover:text-gray-900 dark:text-gray-200 dark:hover:text-gray-100"
|
||||
>
|
||||
{{ element.text }}
|
||||
</router-link>
|
||||
<div class="flex items-center" v-else-if="index === links.length - 1">
|
||||
<svg
|
||||
class="w-6 h-6 text-gray-400 rtl:rotate-180"
|
||||
class="w-6 h-6 text-gray-400 dark:text-gray-100 rtl:rotate-180"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -28,13 +28,13 @@
|
||||
></path>
|
||||
</svg>
|
||||
<span
|
||||
class="ltr:ml-1 rtl:mr-1 font-medium text-gray-600 md:ltr:ml-2 md:rtl:mr-2"
|
||||
class="ltr:ml-1 rtl:mr-1 font-medium text-gray-600 dark:text-gray-300 md:ltr:ml-2 md:rtl:mr-2"
|
||||
>{{ element.text }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center" v-else>
|
||||
<svg
|
||||
class="w-6 h-6 text-gray-400 rtl:rotate-180"
|
||||
class="w-6 h-6 text-gray-400 dark:text-gray-100 rtl:rotate-180"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -47,7 +47,7 @@
|
||||
</svg>
|
||||
<router-link
|
||||
:to="element"
|
||||
class="ltr:ml-1 rtl:mr-1 font-medium text-gray-800 hover:text-gray-900 md:ltr:ml-2 md:rtl:mr-2"
|
||||
class="ltr:ml-1 rtl:mr-1 font-medium text-gray-800 hover:text-gray-900 dark:text-gray-200 dark:hover:text-gray-100 md:ltr:ml-2 md:rtl:mr-2"
|
||||
>{{ element.text }}</router-link
|
||||
>
|
||||
</div>
|
||||
@@ -56,14 +56,12 @@
|
||||
</ol>
|
||||
</nav>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { Location } from "vue-router";
|
||||
<script lang="ts" setup>
|
||||
import { RouteLocationRaw } from "vue-router";
|
||||
|
||||
type LinkElement = Location & { text: string };
|
||||
type LinkElement = RouteLocationRaw & { text: string };
|
||||
|
||||
@Component
|
||||
export default class Breadcrumbs extends Vue {
|
||||
@Prop({ type: Array, required: true }) links!: LinkElement[];
|
||||
}
|
||||
defineProps<{
|
||||
links: LinkElement[];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -1,45 +1,32 @@
|
||||
<template>
|
||||
<div
|
||||
class="empty-content"
|
||||
:class="{ inline, 'text-center': center }"
|
||||
class="flex flex-col items-center mt-80"
|
||||
:class="{ 'mt-40 mb-10': inline, 'text-center': center }"
|
||||
role="note"
|
||||
>
|
||||
<b-icon :icon="icon" size="is-large" />
|
||||
<h2 class="empty-content__title">
|
||||
<o-icon :icon="icon" customSize="48" />
|
||||
<h2 class="mb-3">
|
||||
<!-- @slot Mandatory title -->
|
||||
<slot />
|
||||
</h2>
|
||||
<p v-show="$slots.desc" :class="descriptionClasses">
|
||||
<p v-show="slots.desc" :class="descriptionClasses">
|
||||
<!-- @slot Optional description -->
|
||||
<slot name="desc" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
<script lang="ts" setup>
|
||||
import { useSlots } from "vue";
|
||||
|
||||
@Component
|
||||
export default class EmptyContent extends Vue {
|
||||
@Prop({ type: String, required: true }) icon!: string;
|
||||
@Prop({ type: String, required: false, default: "" })
|
||||
descriptionClasses!: string;
|
||||
@Prop({ type: Boolean, required: false, default: false }) inline!: boolean;
|
||||
@Prop({ type: Boolean, required: false, default: false }) center!: boolean;
|
||||
}
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
icon: string;
|
||||
descriptionClasses?: string;
|
||||
inline?: boolean;
|
||||
center?: boolean;
|
||||
}>(),
|
||||
{ descriptionClasses: "", inline: false, center: false }
|
||||
);
|
||||
|
||||
const slots = useSlots();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.empty-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 20vh;
|
||||
&__title {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
&.inline {
|
||||
margin-top: 5vh;
|
||||
margin-bottom: 2vh;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
<div>a</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
<script lang="ts" setup>
|
||||
import RouteName from "@/router/name";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
@Component
|
||||
export default class HomepageRedirectComponent extends Vue {
|
||||
created(): void {
|
||||
this.$router.replace({ name: RouteName.HOME });
|
||||
}
|
||||
}
|
||||
const router = useRouter();
|
||||
|
||||
router.replace({ name: RouteName.HOME });
|
||||
</script>
|
||||
|
||||
@@ -1,28 +1,35 @@
|
||||
<template>
|
||||
<div class="observer" />
|
||||
<div class="observer" ref="observed" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import "intersection-observer";
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
@Component
|
||||
export default class Observer extends Vue {
|
||||
@Prop({ required: false, default: () => ({}) }) options!: Record<string, any>;
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
options?: Record<string, any>;
|
||||
}>(),
|
||||
{ options: () => ({}) }
|
||||
);
|
||||
|
||||
observer!: IntersectionObserver;
|
||||
mounted(): void {
|
||||
this.observer = new IntersectionObserver(([entry]) => {
|
||||
if (entry && entry.isIntersecting) {
|
||||
this.$emit("intersect");
|
||||
}
|
||||
}, this.options);
|
||||
const observer = ref<IntersectionObserver>();
|
||||
const observed = ref<HTMLElement>();
|
||||
const emit = defineEmits(["intersect"]);
|
||||
|
||||
this.observer.observe(this.$el);
|
||||
onMounted(() => {
|
||||
observer.value = new IntersectionObserver(([entry]) => {
|
||||
if (entry && entry.isIntersecting) {
|
||||
emit("intersect");
|
||||
}
|
||||
}, props.options);
|
||||
|
||||
if (observed.value) {
|
||||
observer.value.observe(observed.value);
|
||||
}
|
||||
});
|
||||
|
||||
destroyed(): void {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
}
|
||||
onUnmounted(() => {
|
||||
observer.value?.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<section class="section container hero is-fullheight">
|
||||
<section class="container mx-auto hero is-fullheight">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<div class="container mx-auto">
|
||||
<div class="columns is-vcentered">
|
||||
<div class="column has-text-centered">
|
||||
<b-button
|
||||
type="is-primary"
|
||||
<o-button
|
||||
variant="primary"
|
||||
size="is-medium"
|
||||
tag="router-link"
|
||||
:to="{
|
||||
@@ -15,105 +15,95 @@
|
||||
redirect: pathAfterLogin,
|
||||
},
|
||||
}"
|
||||
>{{ $t("Login on {instance}", { instance: host }) }}</b-button
|
||||
>{{ $t("Login on {instance}", { instance: host }) }}</o-button
|
||||
>
|
||||
</div>
|
||||
<vertical-divider :content="$t('Or')" />
|
||||
<div class="column">
|
||||
<subtitle>{{
|
||||
$t("I have an account on another Mobilizon instance.")
|
||||
}}</subtitle>
|
||||
<h2 class="text-2xl">
|
||||
{{ $t("I have an account on another Mobilizon instance.") }}
|
||||
</h2>
|
||||
<p>{{ $t("Other software may also support this.") }}</p>
|
||||
<p>{{ sentence }}</p>
|
||||
<form @submit.prevent="redirectToInstance">
|
||||
<b-field :label="$t('Your federated identity')">
|
||||
<b-field>
|
||||
<b-input
|
||||
<o-field :label="$t('Your federated identity')">
|
||||
<o-field>
|
||||
<o-input
|
||||
expanded
|
||||
autocapitalize="none"
|
||||
autocorrect="off"
|
||||
v-model="remoteActorAddress"
|
||||
:placeholder="$t('profile@instance')"
|
||||
></b-input>
|
||||
></o-input>
|
||||
<p class="control">
|
||||
<button class="button is-primary" type="submit">
|
||||
{{ $t("Go") }}
|
||||
</button>
|
||||
</p>
|
||||
</b-field>
|
||||
</b-field>
|
||||
</o-field>
|
||||
</o-field>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="has-text-centered">
|
||||
<b-button tag="a" type="is-text" @click="$router.go(-1)">{{
|
||||
<o-button tag="a" type="is-text" @click="$router.go(-1)">{{
|
||||
$t("Back to previous page")
|
||||
}}</b-button>
|
||||
}}</o-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
<script lang="ts" setup>
|
||||
import VerticalDivider from "@/components/Utils/VerticalDivider.vue";
|
||||
import Subtitle from "@/components/Utils/Subtitle.vue";
|
||||
import { LoginErrorCode } from "@/types/enums";
|
||||
import RouteName from "../../router/name";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
@Component({
|
||||
components: { Subtitle, VerticalDivider },
|
||||
})
|
||||
export default class RedirectWithAccount extends Vue {
|
||||
@Prop({ type: String, required: true }) uri!: string;
|
||||
defineProps<{
|
||||
uri: string;
|
||||
pathAfterLogin?: string;
|
||||
sentence?: string;
|
||||
}>();
|
||||
|
||||
@Prop({ type: String, required: false }) pathAfterLogin!: string;
|
||||
const remoteActorAddress = ref("");
|
||||
|
||||
@Prop({ type: String, required: false }) sentence!: string;
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
const host = computed((): string => {
|
||||
return window.location.hostname;
|
||||
});
|
||||
|
||||
remoteActorAddress = "";
|
||||
const redirectToInstance = async (): Promise<void> => {
|
||||
const [, host] = remoteActorAddress.value.split("@", 2);
|
||||
const remoteInteractionURI = await webFingerFetch(
|
||||
host,
|
||||
remoteActorAddress.value
|
||||
);
|
||||
window.open(remoteInteractionURI);
|
||||
};
|
||||
|
||||
RouteName = RouteName;
|
||||
|
||||
LoginErrorCode = LoginErrorCode;
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
get host(): string {
|
||||
return window.location.hostname;
|
||||
}
|
||||
|
||||
async redirectToInstance(): Promise<void> {
|
||||
const [, host] = this.remoteActorAddress.split("@", 2);
|
||||
const remoteInteractionURI = await this.webFingerFetch(
|
||||
host,
|
||||
this.remoteActorAddress
|
||||
const webFingerFetch = async (
|
||||
hostname: string,
|
||||
identity: string
|
||||
): Promise<string> => {
|
||||
const scheme = process.env.NODE_ENV === "production" ? "https" : "http";
|
||||
const data = await (
|
||||
await fetch(
|
||||
`${scheme}://${hostname}/.well-known/webfinger?resource=acct:${identity}`
|
||||
)
|
||||
).json();
|
||||
if (data && Array.isArray(data.links)) {
|
||||
const link: { template: string } = data.links.find(
|
||||
(someLink: any) =>
|
||||
someLink &&
|
||||
typeof someLink.template === "string" &&
|
||||
someLink.rel === "http://ostatus.org/schema/1.0/subscribe"
|
||||
);
|
||||
window.open(remoteInteractionURI);
|
||||
}
|
||||
|
||||
private async webFingerFetch(
|
||||
hostname: string,
|
||||
identity: string
|
||||
): Promise<string> {
|
||||
const scheme = process.env.NODE_ENV === "production" ? "https" : "http";
|
||||
const data = await (
|
||||
await fetch(
|
||||
`${scheme}://${hostname}/.well-known/webfinger?resource=acct:${identity}`
|
||||
)
|
||||
).json();
|
||||
if (data && Array.isArray(data.links)) {
|
||||
const link: { template: string } = data.links.find(
|
||||
(someLink: any) =>
|
||||
someLink &&
|
||||
typeof someLink.template === "string" &&
|
||||
someLink.rel === "http://ostatus.org/schema/1.0/subscribe"
|
||||
);
|
||||
|
||||
if (link && link.template.includes("{uri}")) {
|
||||
return link.template.replace("{uri}", encodeURIComponent(this.uri));
|
||||
}
|
||||
if (link && link.template.includes("{uri}")) {
|
||||
return link.template.replace("{uri}", encodeURIComponent(this.uri));
|
||||
}
|
||||
throw new Error("No interaction path found in webfinger data");
|
||||
}
|
||||
}
|
||||
throw new Error("No interaction path found in webfinger data");
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<h2>
|
||||
<span>
|
||||
<slot />
|
||||
</span>
|
||||
</h2>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
|
||||
@Component
|
||||
export default class Subtitle extends Vue {}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
h2 {
|
||||
display: block;
|
||||
margin: 15px 0 30px;
|
||||
|
||||
span {
|
||||
background: $secondary;
|
||||
display: inline;
|
||||
padding: 3px 8px;
|
||||
color: #3a384c;
|
||||
font-family: "Liberation Sans", "Helvetica Neue", Roboto, Helvetica, Arial,
|
||||
serif;
|
||||
font-weight: 400;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,17 +1,16 @@
|
||||
<template>
|
||||
<div class="is-divider-vertical" :data-content="dataContent"></div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
@Component
|
||||
export default class VerticalDivider extends Vue {
|
||||
@Prop({ default: "Or" }) content!: string;
|
||||
const props = withDefaults(defineProps<{ content?: string }>(), {
|
||||
content: "Or",
|
||||
});
|
||||
|
||||
get dataContent(): string {
|
||||
return this.content.toLocaleUpperCase();
|
||||
}
|
||||
}
|
||||
const dataContent = computed((): string => {
|
||||
return props.content.toLocaleUpperCase();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.is-divider-vertical[data-content]::after {
|
||||
|
||||
Reference in New Issue
Block a user