Migrate to Vue 3 and Vite

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2022-07-12 10:55:28 +02:00
parent 8f4099ee33
commit ee20e03cc2
464 changed files with 31515 additions and 32758 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 {