build: switch from yarn to npm to manage js dependencies and move js contents to root
yarn v1 is being deprecated and starts to have some issues Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
77
src/components/Account/ActorAutoComplete.vue
Normal file
77
src/components/Account/ActorAutoComplete.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<o-inputitems
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="(val: IActor[]) => $emit('update:modelValue', val)"
|
||||
:data="availableActors"
|
||||
:allow-autocomplete="true"
|
||||
:allow-new="false"
|
||||
:open-on-focus="false"
|
||||
field="displayName"
|
||||
placeholder="Add a recipient"
|
||||
@typing="getActors"
|
||||
>
|
||||
<template #default="props">
|
||||
<ActorInline :actor="props.option" />
|
||||
</template>
|
||||
</o-inputitems>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SEARCH_PERSON_AND_GROUPS } from "@/graphql/search";
|
||||
import { IActor, IGroup, IPerson, displayName } from "@/types/actor";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
import { useLazyQuery } from "@vue/apollo-composable";
|
||||
import { ref } from "vue";
|
||||
import ActorInline from "./ActorInline.vue";
|
||||
|
||||
defineProps<{
|
||||
modelValue: IActor[];
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
"update:modelValue": [value: IActor[]];
|
||||
}>();
|
||||
|
||||
const {
|
||||
load: loadSearchPersonsAndGroupsQuery,
|
||||
refetch: refetchSearchPersonsAndGroupsQuery,
|
||||
} = useLazyQuery<
|
||||
{ searchPersons: Paginate<IPerson>; searchGroups: Paginate<IGroup> },
|
||||
{ searchText: string }
|
||||
>(SEARCH_PERSON_AND_GROUPS);
|
||||
|
||||
const availableActors = ref<IActor[]>([]);
|
||||
|
||||
const getActors = async (text: string) => {
|
||||
availableActors.value = await fetchActors(text);
|
||||
};
|
||||
|
||||
const fetchActors = async (text: string): Promise<IActor[]> => {
|
||||
if (text === "") return [];
|
||||
try {
|
||||
const res =
|
||||
(await loadSearchPersonsAndGroupsQuery(SEARCH_PERSON_AND_GROUPS, {
|
||||
searchText: text,
|
||||
})) ||
|
||||
(
|
||||
await refetchSearchPersonsAndGroupsQuery({
|
||||
searchText: text,
|
||||
})
|
||||
)?.data;
|
||||
if (!res) return [];
|
||||
return [
|
||||
...res.searchPersons.elements.map((person) => ({
|
||||
...person,
|
||||
displayName: displayName(person),
|
||||
})),
|
||||
...res.searchGroups.elements.map((group) => ({
|
||||
...group,
|
||||
displayName: displayName(group),
|
||||
})),
|
||||
];
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
</script>
|
||||
52
src/components/Account/ActorCard.story.vue
Normal file
52
src/components/Account/ActorCard.story.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<Story>
|
||||
<Variant title="local">
|
||||
<ActorCard :actor="stateLocal"></ActorCard>
|
||||
<template #controls>
|
||||
<HstText v-model="stateLocal.preferredUsername" title="username" />
|
||||
<HstText v-model="stateLocal.name" title="Name" />
|
||||
</template>
|
||||
</Variant>
|
||||
<Variant title="remote">
|
||||
<ActorCard :actor="stateRemote"></ActorCard>
|
||||
<template #controls>
|
||||
<HstText v-model="stateRemote.preferredUsername" title="username" />
|
||||
<HstText v-model="stateRemote.name" title="Name" />
|
||||
<HstText v-model="stateRemote.domain" title="Domain" />
|
||||
<HstText v-model="avatarUrl" title="Avatar" />
|
||||
</template>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ActorCard from "./ActorCard.vue";
|
||||
import { reactive, ref } from "vue";
|
||||
import { IActor } from "@/types/actor";
|
||||
import { ActorType } from "@/types/enums";
|
||||
|
||||
const avatarUrl = ref<string>(
|
||||
"https://stockage.framapiaf.org/framapiaf/accounts/avatars/000/000/399/original/52b08a3e80b43d40.jpg"
|
||||
);
|
||||
|
||||
const stateLocal = reactive<IActor>({
|
||||
name: "Thomas Citharel",
|
||||
preferredUsername: "tcit",
|
||||
avatar: null,
|
||||
domain: null,
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
});
|
||||
|
||||
const stateRemote = reactive<IActor>({
|
||||
name: "Framasoft",
|
||||
preferredUsername: "framasoft",
|
||||
avatar: { url: avatarUrl.value, id: "", name: "", alt: "", metadata: {} },
|
||||
domain: "framapiaf.org",
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
});
|
||||
</script>
|
||||
109
src/components/Account/ActorCard.vue
Normal file
109
src/components/Account/ActorCard.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div
|
||||
class="bg-white dark:bg-mbz-purple rounded-lg flex space-x-4 items-center"
|
||||
:class="{ 'flex-col p-4 shadow-md sm:p-8 pb-10 w-80': !inline }"
|
||||
>
|
||||
<div class="flex pl-2">
|
||||
<figure class="w-12 h-12" v-if="actor.avatar">
|
||||
<img
|
||||
class="rounded-full object-cover h-full"
|
||||
:src="actor.avatar.url"
|
||||
alt=""
|
||||
width="48"
|
||||
height="48"
|
||||
loading="lazy"
|
||||
/>
|
||||
</figure>
|
||||
<AccountCircle
|
||||
v-else
|
||||
:size="inline ? 24 : 48"
|
||||
class="ltr:-mr-0.5 rtl:-ml-0.5"
|
||||
/>
|
||||
</div>
|
||||
<div :class="{ 'text-center': !inline }" class="overflow-hidden w-full">
|
||||
<h5
|
||||
class="text-xl font-medium violet-title tracking-tight text-gray-900 dark:text-gray-200 whitespace-pre-line line-clamp-2"
|
||||
>
|
||||
{{ displayName(actor) }}
|
||||
</h5>
|
||||
<p class="text-gray-500 dark:text-gray-200 truncate" v-if="actor.name">
|
||||
<span dir="ltr">@{{ usernameWithDomain(actor) }}</span>
|
||||
</p>
|
||||
<div
|
||||
v-if="full"
|
||||
class="only-first-child"
|
||||
:class="{
|
||||
'line-clamp-3': limit,
|
||||
'line-clamp-10': !limit,
|
||||
}"
|
||||
v-html="actor.summary"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex pr-2">
|
||||
<Email />
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div
|
||||
class="p-4 bg-white rounded-lg shadow-md sm:p-8 flex items-center space-x-4"
|
||||
dir="auto"
|
||||
>
|
||||
<div class="flex-shrink-0">
|
||||
<figure class="w-12 h-12" v-if="actor.avatar">
|
||||
<img
|
||||
class="rounded-lg"
|
||||
:src="actor.avatar.url"
|
||||
alt=""
|
||||
width="48"
|
||||
height="48"
|
||||
/>
|
||||
</figure>
|
||||
<o-icon
|
||||
v-else
|
||||
size="large"
|
||||
icon="account-circle"
|
||||
class="ltr:-mr-0.5 rtl:-ml-0.5"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h5 class="text-xl font-medium violet-title tracking-tight text-gray-900">
|
||||
{{ displayName(actor) }}
|
||||
</h5>
|
||||
<p class="text-gray-500 truncate" v-if="actor.name">
|
||||
<span dir="ltr">@{{ usernameWithDomain(actor) }}</span>
|
||||
</p>
|
||||
<div
|
||||
v-if="full"
|
||||
class="line-clamp-3"
|
||||
:class="{ limit: limit }"
|
||||
v-html="actor.summary"
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { displayName, IActor, usernameWithDomain } from "../../types/actor";
|
||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||
import Email from "vue-material-design-icons/Email.vue";
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
actor: IActor;
|
||||
full?: boolean;
|
||||
inline?: boolean;
|
||||
popover?: boolean;
|
||||
limit?: boolean;
|
||||
}>(),
|
||||
{
|
||||
full: false,
|
||||
inline: false,
|
||||
popover: false,
|
||||
limit: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style scoped>
|
||||
.only-first-child :deep(:not(:first-child)) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
52
src/components/Account/ActorInline.story.vue
Normal file
52
src/components/Account/ActorInline.story.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<Story>
|
||||
<Variant title="local">
|
||||
<ActorInline :actor="stateLocal" />
|
||||
<template #controls>
|
||||
<HstText v-model="stateLocal.preferredUsername" title="username" />
|
||||
<HstText v-model="stateLocal.name" title="Name" />
|
||||
</template>
|
||||
</Variant>
|
||||
<Variant title="remote">
|
||||
<ActorInline :actor="stateRemote" />
|
||||
<template #controls>
|
||||
<HstText v-model="stateRemote.preferredUsername" title="username" />
|
||||
<HstText v-model="stateRemote.name" title="Name" />
|
||||
<HstText v-model="stateRemote.domain" title="Domain" />
|
||||
<HstText v-model="avatarUrl" title="Avatar" />
|
||||
</template>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import ActorInline from "./ActorInline.vue";
|
||||
import { reactive, ref } from "vue";
|
||||
import { IActor } from "@/types/actor";
|
||||
import { ActorType } from "@/types/enums";
|
||||
|
||||
const avatarUrl = ref<string>(
|
||||
"https://stockage.framapiaf.org/framapiaf/accounts/avatars/000/000/399/original/52b08a3e80b43d40.jpg"
|
||||
);
|
||||
|
||||
const stateLocal = reactive<IActor>({
|
||||
name: "Thomas Citharel",
|
||||
preferredUsername: "tcit",
|
||||
avatar: null,
|
||||
domain: null,
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
});
|
||||
|
||||
const stateRemote = reactive<IActor>({
|
||||
name: "Framasoft",
|
||||
preferredUsername: "framasoft",
|
||||
avatar: { url: avatarUrl.value, id: "", name: "", alt: "", metadata: {} },
|
||||
domain: "framapiaf.org",
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
});
|
||||
</script>
|
||||
62
src/components/Account/ActorInline.vue
Normal file
62
src/components/Account/ActorInline.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div
|
||||
class="inline-flex items-start gap-2 bg-white dark:bg-violet-1 dark:text-white p-2 rounded-md"
|
||||
>
|
||||
<div class="flex-none">
|
||||
<figure v-if="actor.avatar">
|
||||
<img
|
||||
class="rounded-xl"
|
||||
:src="actor.avatar.url"
|
||||
alt=""
|
||||
width="36"
|
||||
height="36"
|
||||
loading="lazy"
|
||||
/>
|
||||
</figure>
|
||||
<AccountCircle :size="36" v-else />
|
||||
</div>
|
||||
|
||||
<div class="flex-auto">
|
||||
<p class="text-lg line-clamp-3 md:line-clamp-2 max-w-xl">
|
||||
{{ displayName(actor) }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-300 truncate">
|
||||
@{{ usernameWithDomain(actor) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex pr-2 self-center">
|
||||
<Email />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { displayName, IActor, usernameWithDomain } from "../../types/actor";
|
||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||
import Email from "vue-material-design-icons/Email.vue";
|
||||
|
||||
defineProps<{
|
||||
actor: IActor;
|
||||
}>();
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@use "@/styles/_mixins" as *;
|
||||
div.actor-inline {
|
||||
align-items: flex-start;
|
||||
display: inline-flex;
|
||||
text-align: inherit;
|
||||
align-items: top;
|
||||
|
||||
div.actor-avatar {
|
||||
flex-basis: auto;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
// @include margin-right(0.5rem);
|
||||
}
|
||||
div.actor-name {
|
||||
flex-basis: auto;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
text-align: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
59
src/components/Account/PopoverActorCard.story.vue
Normal file
59
src/components/Account/PopoverActorCard.story.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<Story>
|
||||
<Variant :setup-app="setupApp" title="Person">
|
||||
<div class="p-5">
|
||||
<PopoverActorCard :actor="baseActor">
|
||||
<div><b> Popover me !</b></div></PopoverActorCard
|
||||
>
|
||||
</div>
|
||||
</Variant>
|
||||
<Variant :setup-app="setupApp" title="Group">
|
||||
<div class="p-5">
|
||||
<PopoverActorCard :actor="group">
|
||||
<div><b> Popover me !</b></div></PopoverActorCard
|
||||
>
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PopoverActorCard from "./PopoverActorCard.vue";
|
||||
import FloatingVue from "floating-vue";
|
||||
import "floating-vue/dist/style.css";
|
||||
import { ActorType } from "@/types/enums";
|
||||
|
||||
const baseActorAvatar = {
|
||||
id: "",
|
||||
name: "",
|
||||
alt: "",
|
||||
metadata: {},
|
||||
url: "https://social.tcit.fr/system/accounts/avatars/000/000/001/original/a28c50ce5f2b13fd.jpg",
|
||||
};
|
||||
|
||||
const baseActor = {
|
||||
name: "Thomas Citharel",
|
||||
preferredUsername: "tcit",
|
||||
avatar: baseActorAvatar,
|
||||
domain: null,
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
};
|
||||
|
||||
const group = {
|
||||
...baseActor,
|
||||
name: "Framasoft",
|
||||
preferredUsername: "framasoft",
|
||||
domain: "mobilizon.fr",
|
||||
avatar: {
|
||||
...baseActorAvatar,
|
||||
url: "https://stockage.framapiaf.org/framapiaf/accounts/avatars/000/000/399/original/52b08a3e80b43d40.jpg",
|
||||
},
|
||||
};
|
||||
|
||||
function setupApp({ app }) {
|
||||
app.use(FloatingVue);
|
||||
}
|
||||
</script>
|
||||
28
src/components/Account/PopoverActorCard.vue
Normal file
28
src/components/Account/PopoverActorCard.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<VMenu
|
||||
:distance="16"
|
||||
:triggers="['hover']"
|
||||
class="popover"
|
||||
:class="{ inline, clickable: actor && actor.type === ActorType.GROUP }"
|
||||
>
|
||||
<slot></slot>
|
||||
<template #popper>
|
||||
<actor-card :full="true" :actor="actor" :popover="true" />
|
||||
</template>
|
||||
</VMenu>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ActorType } from "@/types/enums";
|
||||
import { IActor } from "../../types/actor";
|
||||
import ActorCard from "./ActorCard.vue";
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
actor: IActor;
|
||||
inline?: boolean;
|
||||
}>(),
|
||||
{
|
||||
inline: false,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
29
src/components/Account/ProfileOnboarding.story.vue
Normal file
29
src/components/Account/ProfileOnboarding.story.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<Story>
|
||||
<Variant>
|
||||
<div class="p-5">
|
||||
<ProfileOnboarding
|
||||
:current-actor="baseActor"
|
||||
instance-name="Instance name"
|
||||
/>
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ProfileOnboarding from "./ProfileOnboarding.vue";
|
||||
import { ActorType } from "@/types/enums";
|
||||
import { IPerson } from "@/types/actor";
|
||||
|
||||
const baseActor: IPerson = {
|
||||
name: "Thomas Citharel",
|
||||
preferredUsername: "tcit",
|
||||
avatar: null,
|
||||
domain: null,
|
||||
url: "",
|
||||
summary: "",
|
||||
suspended: false,
|
||||
type: ActorType.PERSON,
|
||||
};
|
||||
</script>
|
||||
60
src/components/Account/ProfileOnboarding.vue
Normal file
60
src/components/Account/ProfileOnboarding.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<h2 class="text-2xl">{{ t("Profiles and federation") }}</h2>
|
||||
</div>
|
||||
<p class="my-2">
|
||||
{{
|
||||
t(
|
||||
"Mobilizon uses a system of profiles to compartiment your activities. You will be able to create as many profiles as you want."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<hr role="presentation" />
|
||||
<p class="my-2">
|
||||
<span>
|
||||
{{
|
||||
t(
|
||||
"Mobilizon is a federated software, meaning you can interact - depending on your admin's federation settings - with content from other instances, such as joining groups or events that were created elsewhere."
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<i18n-t
|
||||
keypath="This instance, {instanceName}, hosts your profile, so remember its name."
|
||||
>
|
||||
<template #instanceName>
|
||||
<b>{{
|
||||
t("{instanceName} ({domain})", {
|
||||
domain,
|
||||
instanceName,
|
||||
})
|
||||
}}</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</p>
|
||||
<hr role="presentation" />
|
||||
<p class="my-2">
|
||||
{{
|
||||
t(
|
||||
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:"
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<div class="text-center">
|
||||
<code>{{ `${currentActor?.preferredUsername}@${domain}` }}</code>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { IPerson } from "@/types/actor";
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
defineProps<{
|
||||
currentActor: IPerson;
|
||||
instanceName: string;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
|
||||
const domain = computed(() => window.location.hostname);
|
||||
</script>
|
||||
Reference in New Issue
Block a user