Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-11-18 17:50:54 +01:00
parent 32a63a693d
commit dccf397df5
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
217 changed files with 3843 additions and 6061 deletions

View File

@ -15,13 +15,7 @@ config :mobilizon, Mobilizon.Web.Endpoint,
check_origin: false,
watchers: [
node: [
"node_modules/webpack/bin/webpack.js",
"--mode",
"development",
"--watch",
"--watch-options-stdin",
"--config",
"node_modules/@vue/cli-service/webpack.config.js",
"node_modules/.bin/vite",
cd: Path.expand("../js", __DIR__)
]
]

View File

@ -3,12 +3,13 @@
"version": "2.0.0-beta.2",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"dev": "tsc --noEmit && vite",
"serve": "vite preview",
"build": "yarn run build:assets && yarn run build:pictures",
"test:unit": "LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 TZ=UTC vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e",
"lint": "vue-cli-service lint",
"build:assets": "vue-cli-service build",
"build:assets": "vite build",
"build:pictures": "bash ./scripts/build/pictures.sh"
},
"dependencies": {
@ -18,6 +19,7 @@
"@mdi/font": "^6.1.95",
"@oruga-ui/oruga-next": "^0.4.7",
"@oruga-ui/theme-bulma": "^0.1.3",
"@popperjs/core": "^2.10.2",
"@tiptap/core": "^2.0.0-beta.41",
"@tiptap/extension-blockquote": "^2.0.0-beta.6",
"@tiptap/extension-bubble-menu": "^2.0.0-beta.9",
@ -31,13 +33,16 @@
"@tiptap/extension-underline": "^2.0.0-beta.7",
"@tiptap/starter-kit": "^2.0.0-beta.37",
"@tiptap/vue-3": "^2.0.0-beta.81",
"@variantjs/core": "^0.0.78",
"@variantjs/vue": "^0.0.12",
"@vue-a11y/announcer": "^3.1.5",
"@vue-a11y/skip-to": "^3.0.3",
"@vue-leaflet/vue-leaflet": "^0.6.1",
"@vue/apollo-composable": "^4.0.0-alpha.15",
"@vue/apollo-option": "^4.0.0-alpha.15",
"apollo-absinthe-upload-link": "^1.5.0",
"blurhash": "^1.1.3",
"body-scroll-lock": "^4.0.0-beta.0",
"bulma": "^0.9.3",
"bulma-divider": "^0.2.0",
"core-js": "^3.6.4",
"date-fns": "^2.16.0",
@ -67,11 +72,14 @@
"vuedraggable": "^2.24.3"
},
"devDependencies": {
"@originjs/vite-plugin-commonjs": "^1.0.1",
"@rollup/plugin-dynamic-import-vars": "^1.4.1",
"@types/jest": "^27.0.2",
"@types/leaflet": "^1.5.2",
"@types/leaflet.locatecontrol": "^0.60.7",
"@types/lodash": "^4.14.141",
"@types/ngeohash": "^0.6.2",
"@types/node": "^16.11.7",
"@types/phoenix": "^1.5.2",
"@types/prosemirror-inputrules": "^1.0.2",
"@types/prosemirror-model": "^1.7.2",
@ -80,18 +88,12 @@
"@types/sanitize-html": "^2.5.0",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"@vue/cli-plugin-babel": "~5.0.0-rc.1",
"@vue/cli-plugin-e2e-cypress": "~5.0.0-rc.1",
"@vue/cli-plugin-eslint": "~5.0.0-rc.1",
"@vue/cli-plugin-pwa": "~5.0.0-rc.1",
"@vue/cli-plugin-router": "~5.0.0-rc.1",
"@vue/cli-plugin-typescript": "~5.0.0-rc.1",
"@vue/cli-plugin-unit-jest": "~5.0.0-rc.1",
"@vue/cli-service": "~5.0.0-rc.1",
"@vitejs/plugin-vue": "^1.9.4",
"@vue/compiler-sfc": "^3.1.0",
"@vue/eslint-config-typescript": "^9.0.0",
"@vue/test-utils": "^2.0.0-rc.16",
"@vue/vue3-jest": "^27.0.0-alpha.3",
"autoprefixer": "^10.4.0",
"babel-core": "^7.0.0-bridge.0",
"cypress": "^8.3.0",
"eslint": "^8.2.0",
@ -104,12 +106,17 @@
"jest": "^27.1.0",
"jest-junit": "^13.0.0",
"mock-apollo-client": "^1.1.0",
"postcss": "^8.3.11",
"prettier": "^2.2.1",
"prettier-eslint": "^13.0.0",
"sass": "^1.34.1",
"sass": "^1.43.4",
"sass-loader": "^12.0.0",
"tailwindcss": "^2.2.19",
"ts-jest": "27",
"typescript": "~4.4.3",
"typescript": "^4.5.2",
"vite": "^2.6.14",
"vite-plugin-html": "^2.1.1",
"vite-plugin-pwa": "^0.11.5",
"vue-i18n-extract": "^2.0.4",
"webpack-cli": "^4.7.0"
}

6
js/postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,22 +1,25 @@
<!DOCTYPE html>
<html lang="en" dir="auto">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<meta name="server-injected-data" />
</head>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<meta name="server-injected-data" />
<script type="module" src="http://localhost:3000/@vite/client"></script>
<script type="module" src="http://localhost:3000/src/main.ts"></script>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -26,7 +26,7 @@
<main id="main" v-else>
<transition name="fade" mode="out-in">
<!-- <router-view ref="routerView" /> -->
<router-view ref="routerView" />
</transition>
</main>
<mobilizon-footer />
@ -53,7 +53,12 @@ import { ICurrentUser } from "./types/current-user.model";
import jwt_decode, { JwtPayload } from "jwt-decode";
import { refreshAccessToken } from "./apollo/utils";
import { defineComponent, ref } from "vue";
import { useMutation, useQuery, useResult } from "@vue/apollo-composable";
import {
useApolloClient,
useMutation,
useQuery,
useResult,
} from "@vue/apollo-composable";
import { RouterView } from "vue-router";
import { useAnnouncer } from "@vue-a11y/announcer";
import { useMeta } from "vue-meta";
@ -76,6 +81,8 @@ export default defineComponent({
useMeta({ titleTemplate: "%s | Mobilizon" });
const { resolveClient } = useApolloClient();
return {
config,
currentUser,
@ -83,6 +90,7 @@ export default defineComponent({
online: false,
interval,
routerView,
resolveClient,
};
},
components: {
@ -91,11 +99,6 @@ export default defineComponent({
import(/* webpackChunkName: "editor" */ "./components/Error.vue"),
"mobilizon-footer": Footer,
},
async created(): Promise<void> {
if (await this.initializeCurrentUser()) {
await initializeCurrentActor(this.$apollo.provider.defaultClient);
}
},
methods: {
async initializeCurrentUser(): Promise<boolean> {
const userId = localStorage.getItem(AUTH_USER_ID);
@ -145,7 +148,10 @@ export default defineComponent({
return document.title;
},
},
mounted(): void {
async mounted(): Promise<void> {
if (await this.initializeCurrentUser()) {
await initializeCurrentActor(this.resolveClient());
}
this.online = window.navigator.onLine;
window.addEventListener("offline", () => {
this.online = false;
@ -192,7 +198,7 @@ export default defineComponent({
token?.exp !== undefined &&
new Date(token.exp * 1000 - 60000) < new Date()
) {
refreshAccessToken(this.$apollo.getClient());
refreshAccessToken(this.resolveClient());
}
}
}, 60000);
@ -237,7 +243,7 @@ export default defineComponent({
/* Icons */
$mdi-font-path: "~@mdi/font/fonts";
@import "~@mdi/font/scss/materialdesignicons";
@import "../node_modules/@mdi/font/scss/materialdesignicons";
@import "common";
#mobilizon {

View File

@ -1,9 +1,9 @@
@use "@/styles/_mixins" as *;
@import "variables.scss";
@import "~bulma";
@import "~bulma-divider";
@import "~buefy/src/scss/buefy";
// @import "../node_modules/bulma/bulma.sass";
// @import "../node_modules/bulma-divider/dist/css/bulma-divider.sass";
// @import "~buefy/src/scss/buefy";
@import "styles/vue-announcer.scss";
@import "styles/vue-skip-to.scss";

View File

@ -8,9 +8,8 @@
</p>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class InstanceContactLink extends Vue {
@Prop({ required: true, type: String }) contact!: string;

View File

@ -36,15 +36,10 @@
</div>
</div>
</template>
<template slot="footer">
<span class="has-text-grey" v-show="page > totalPages">
Thats it! No more movies found.
</span>
</template>
</o-autocomplete>
</template>
<script lang="ts">
import { Component, Model, Vue, Watch } from "vue-property-decorator";
import { Model, Vue, Watch } from "vue-property-decorator";
import debounce from "lodash/debounce";
import { IPerson } from "@/types/actor";
import { SEARCH_PERSONS } from "@/graphql/search";
@ -52,7 +47,6 @@ import { Paginate } from "@/types/paginate";
const SEARCH_PERSON_LIMIT = 10;
@Component
export default class ActorAutoComplete extends Vue {
@Model("change", { type: Object }) readonly defaultSelected!: IPerson | null;

View File

@ -24,10 +24,9 @@
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { Vue, Prop } from "vue-property-decorator";
import { IActor, usernameWithDomain } from "../../types/actor";
@Component
export default class ActorCard extends Vue {
@Prop({ required: true, type: Object }) actor!: IActor;

View File

@ -15,10 +15,9 @@
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { Vue, Prop } from "vue-property-decorator";
import { IActor, usernameWithDomain } from "../../types/actor";
@Component
export default class ActorInline extends Vue {
@Prop({ required: true, type: Object }) actor!: IActor;

View File

@ -37,11 +37,11 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { IDENTITIES } from "../../graphql/actor";
import { IPerson, Person } from "../../types/actor";
@Component({
@Options({
apollo: {
identities: {
query: IDENTITIES,

View File

@ -13,11 +13,11 @@
</template>
<script lang="ts">
import { ActorType } from "@/types/enums";
import { Component, Vue, Prop } from "vue-property-decorator";
import { Options, Vue, Prop } from "vue-property-decorator";
import { IActor } from "../../types/actor";
import ActorCard from "./ActorCard.vue";
@Component({
@Options({
components: {
ActorCard,
},

View File

@ -53,9 +53,9 @@ import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
import { CONFIG } from "@/graphql/config";
import { IPerson } from "@/types/actor";
import { IConfig } from "@/types/config.model";
import { Component, Vue } from "vue-property-decorator";
import { Options, Vue } from "vue-property-decorator";
@Component({
@Options({
apollo: {
config: CONFIG,
currentActor: CURRENT_ACTOR_CLIENT,

View File

@ -54,13 +54,13 @@
<script lang="ts">
import { usernameWithDomain } from "@/types/actor";
import { ActivityDiscussionSubject } from "@/types/enums";
import { Component } from "vue-property-decorator";
import { Options } from "vue-property-decorator";
import RouteName from "../../router/name";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
import ActivityMixin from "../../mixins/activity";
import { mixins } from "vue-class-component";
@Component({
@Options({
components: {
PopoverActorCard,
},

View File

@ -40,12 +40,12 @@ import {
ActivityEventSubject,
} from "@/types/enums";
import { mixins } from "vue-class-component";
import { Component } from "vue-property-decorator";
import { Options } from "vue-property-decorator";
import RouteName from "../../router/name";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
import ActivityMixin from "../../mixins/activity";
@Component({
@Options({
components: {
PopoverActorCard,
},

View File

@ -72,13 +72,13 @@
<script lang="ts">
import { usernameWithDomain } from "@/types/actor";
import { ActivityGroupSubject, GroupVisibility, Openness } from "@/types/enums";
import { Component } from "vue-property-decorator";
import { Options } from "vue-property-decorator";
import RouteName from "../../router/name";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
import ActivityMixin from "../../mixins/activity";
import { mixins } from "vue-class-component";
@Component({
@Options({
components: {
PopoverActorCard,
},

View File

@ -31,7 +31,7 @@
<script lang="ts">
import { displayName } from "@/types/actor";
import { ActivityMemberSubject, MemberRole } from "@/types/enums";
import { Component } from "vue-property-decorator";
import { Options } from "vue-property-decorator";
import RouteName from "../../router/name";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
import ActivityMixin from "../../mixins/activity";
@ -44,7 +44,7 @@ export const MEMBER_ROLE_VALUE: Record<string, number> = {
[MemberRole.CREATOR]: 100,
};
@Component({
@Options({
components: {
PopoverActorCard,
},

View File

@ -36,13 +36,13 @@
<script lang="ts">
import { usernameWithDomain } from "@/types/actor";
import { ActivityPostSubject } from "@/types/enums";
import { Component } from "vue-property-decorator";
import { Options } from "vue-property-decorator";
import RouteName from "../../router/name";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
import ActivityMixin from "../../mixins/activity";
import { mixins } from "vue-class-component";
@Component({
@Options({
components: {
PopoverActorCard,
},

View File

@ -46,14 +46,14 @@
<script lang="ts">
import { usernameWithDomain } from "@/types/actor";
import { ActivityResourceSubject } from "@/types/enums";
import { Component } from "vue-property-decorator";
import { Options } from "vue-property-decorator";
import RouteName from "../../router/name";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
import ActivityMixin from "../../mixins/activity";
import { mixins } from "vue-class-component";
import { Location } from "vue-router";
@Component({
@Options({
components: {
PopoverActorCard,
},

View File

@ -44,9 +44,8 @@
<script lang="ts">
import { IAddress } from "@/types/address.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class AddressInfo extends Vue {
@Prop({ required: true, type: Object as PropType<IAddress> })
address!: IAddress;

View File

@ -20,9 +20,8 @@
<script lang="ts">
import { IAddress } from "@/types/address.model";
import { PropType } from "vue";
import { Prop, Vue, Component } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class InlineAddress extends Vue {
@Prop({ required: true, type: Object as PropType<IAddress> })
physicalAddress!: IAddress;

View File

@ -128,8 +128,8 @@
</div>
</template>
<script lang="ts">
import { Component, Mixins } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { Options, mixins } from "vue-property-decorator";
// import { SnackbarProgrammatic as Snackbar } from "buefy";
import { formatDistanceToNow } from "date-fns";
import {
ACCEPT_RELAY,
@ -143,7 +143,7 @@ import { Paginate } from "@/types/paginate";
const FOLLOWERS_PER_PAGE = 10;
@Component({
@Options({
apollo: {
relayFollowers: {
query: RELAY_FOLLOWERS,
@ -162,7 +162,7 @@ const FOLLOWERS_PER_PAGE = 10;
};
},
})
export default class Followers extends Mixins(RelayMixin) {
export default class Followers extends mixins(RelayMixin) {
RelayMixin = RelayMixin;
formatDistanceToNow = formatDistanceToNow;
@ -211,11 +211,11 @@ export default class Followers extends Mixins(RelayMixin) {
this.checkedRows = [];
} catch (e: any) {
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
// Snackbar.open({
// message: e.message,
// type: "is-danger",
// position: "is-bottom",
// });
}
}
}
@ -232,11 +232,11 @@ export default class Followers extends Mixins(RelayMixin) {
this.checkedRows = [];
} catch (e: any) {
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
// Snackbar.open({
// message: e.message,
// type: "is-danger",
// position: "is-bottom",
// });
}
}
}

View File

@ -138,8 +138,8 @@
</div>
</template>
<script lang="ts">
import { Component, Mixins } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { Options, mixins } from "vue-property-decorator";
// import { SnackbarProgrammatic as Snackbar } from "buefy";
import { formatDistanceToNow } from "date-fns";
import { ADD_RELAY, REMOVE_RELAY } from "../../graphql/admin";
import { IFollower } from "../../types/actor/follower.model";
@ -152,7 +152,7 @@ import gql from "graphql-tag";
const FOLLOWINGS_PER_PAGE = 10;
@Component({
@Options({
apollo: {
relayFollowings: {
query: RELAY_FOLLOWINGS,
@ -171,7 +171,7 @@ const FOLLOWINGS_PER_PAGE = 10;
};
},
})
export default class Followings extends Mixins(RelayMixin) {
export default class Followings extends mixins(RelayMixin) {
newRelayAddress = "";
RelayMixin = RelayMixin;
@ -256,11 +256,11 @@ export default class Followings extends Mixins(RelayMixin) {
this.newRelayAddress = "";
} catch (err: any) {
if (err.message) {
Snackbar.open({
message: err.message,
type: "is-danger",
position: "is-bottom",
});
// Snackbar.open({
// message: err.message,
// type: "is-danger",
// position: "is-bottom",
// });
}
}
}

View File

@ -179,9 +179,9 @@
</li>
</template>
<script lang="ts">
import { Component, Prop, Vue, Ref } from "vue-property-decorator";
import { Options, Prop, Vue, Ref } from "vue-property-decorator";
import EditorComponent from "@/components/Editor.vue";
import { SnackbarProgrammatic as Snackbar } from "buefy";
// import { SnackbarProgrammatic as Snackbar } from "buefy";
import { formatDistanceToNow } from "date-fns";
import { CommentModeration } from "@/types/enums";
import { CommentModel, IComment } from "../../types/comment.model";
@ -193,7 +193,7 @@ import { IReport } from "../../types/report.model";
import { CREATE_REPORT } from "../../graphql/report";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
@Component({
@Options({
apollo: {
currentActor: {
query: CURRENT_ACTOR_CLIENT,
@ -295,7 +295,7 @@ export default class Comment extends Vue {
reportModal(): void {
if (!this.comment.actor) return;
this.$buefy.modal.open({
this.$oruga.modal.open({
parent: this,
component: ReportModal,
props: {
@ -324,7 +324,7 @@ export default class Comment extends Vue {
forward,
},
});
this.$buefy.notification.open({
this.$oruga.notification.open({
message: this.$t("Comment from @{username} reported", {
username: this.comment.actor.preferredUsername,
}) as string,
@ -334,11 +334,11 @@ export default class Comment extends Vue {
});
} catch (e: any) {
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
// Snackbar.open({
// message: e.message,
// type: "is-danger",
// position: "is-bottom",
// });
}
}
}

View File

@ -85,7 +85,7 @@
</template>
<script lang="ts">
import { Prop, Vue, Component, Watch } from "vue-property-decorator";
import { Prop, Vue, Options, Watch } from "vue-property-decorator";
import Comment from "@/components/Comment/Comment.vue";
import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue";
import { CommentModeration } from "@/types/enums";
@ -101,7 +101,7 @@ import { IEvent } from "../../types/event.model";
import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
import EmptyContent from "@/components/Utils/EmptyContent.vue";
@Component({
@Options({
apollo: {
currentActor: CURRENT_ACTOR_CLIENT,
comments: {
@ -366,7 +366,7 @@ export default class CommentTree extends Vue {
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@import "~bulma/sass/utilities/mixins.sass";
@import "../node_modules/bulma/sass/utilities/mixins.sass";
form.new-comment {
padding-bottom: 1rem;

View File

@ -109,13 +109,13 @@
</article>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { formatDistanceToNow } from "date-fns";
import { IComment } from "../../types/comment.model";
import { usernameWithDomain, IPerson } from "../../types/actor";
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
@Component({
@Options({
apollo: {
currentActor: CURRENT_ACTOR_CLIENT,
},

View File

@ -50,12 +50,11 @@
</router-link>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
import { formatDistanceToNowStrict } from "date-fns";
import { IDiscussion } from "../../types/discussions";
import RouteName from "../../router/name";
@Component
export default class DiscussionListItem extends Vue {
@Prop({ required: true, type: Object }) discussion!: IDiscussion;

View File

@ -193,8 +193,8 @@
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { Editor, EditorContent, BubbleMenu } from "@tiptap/vue-2";
import { Options, Prop, Vue, Watch } from "vue-property-decorator";
import { Editor, EditorContent, BubbleMenu } from "@tiptap/vue-3";
import StarterKit from "@tiptap/starter-kit";
import Document from "@tiptap/extension-document";
import Paragraph from "@tiptap/extension-paragraph";
@ -214,7 +214,7 @@ import CharacterCount from "@tiptap/extension-character-count";
import { AutoDir } from "./Editor/Autodir";
import sanitizeHtml from "sanitize-html";
@Component({
@Options({
components: { EditorContent, BubbleMenu },
apollo: {
currentActor: {
@ -319,17 +319,17 @@ export default class EditorComponent extends Vue {
* Show a popup to get the link from the URL
*/
showLinkMenu(): void {
this.$buefy.dialog.prompt({
message: this.$t("Enter the link URL") as string,
inputAttrs: {
type: "url",
},
trapFocus: true,
onConfirm: (value) => {
if (!this.editor) return undefined;
this.editor.chain().focus().setLink({ href: value }).run();
},
});
// this.$buefy.dialog.prompt({
// message: this.$t("Enter the link URL") as string,
// inputAttrs: {
// type: "url",
// },
// trapFocus: true,
// onConfirm: (value) => {
// if (!this.editor) return undefined;
// this.editor.chain().focus().setLink({ href: value }).run();
// },
// });
}
/**

View File

@ -1,5 +1,5 @@
import { UPLOAD_MEDIA } from "@/graphql/upload";
import apolloProvider from "@/vue-apollo";
import { apolloClient } from "@/vue-apollo";
import { ApolloClient } from "@apollo/client/core/ApolloClient";
import { Plugin } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
@ -61,7 +61,7 @@ const CustomImage = Image.extend({
});
if (!coordinates) return false;
const client =
apolloProvider.defaultClient as ApolloClient<NormalizedCacheObject>;
apolloClient as ApolloClient<NormalizedCacheObject>;
try {
images.forEach(async (image) => {

View File

@ -1,15 +1,14 @@
import { SEARCH_PERSONS } from "@/graphql/search";
import { VueRenderer } from "@tiptap/vue-2";
import { VueRenderer } from "@tiptap/vue-3";
import tippy from "tippy.js";
import MentionList from "./MentionList.vue";
import { ApolloClient } from "@apollo/client/core/ApolloClient";
import apolloProvider from "@/vue-apollo";
import { apolloClient } from "@/vue-apollo";
import { IPerson } from "@/types/actor";
import pDebounce from "p-debounce";
import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types";
const client =
apolloProvider.defaultClient as ApolloClient<NormalizedCacheObject>;
const client = apolloClient as ApolloClient<NormalizedCacheObject>;
const fetchItems = async (query: string): Promise<IPerson[]> => {
const result = await client.query({
@ -43,6 +42,8 @@ const mentionOptions: Partial<any> = {
return {
onStart: (props: any) => {
component = new VueRenderer(MentionList, {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
parent: this,
propsData: props,
});

View File

@ -13,12 +13,12 @@
</template>
<script lang="ts">
import { Vue, Component, Prop, Watch } from "vue-property-decorator";
import { Vue, Options, Prop, Watch } from "vue-property-decorator";
import { displayName, usernameWithDomain } from "@/types/actor/actor.model";
import { IPerson } from "@/types/actor";
import ActorCard from "../../components/Account/ActorCard.vue";
@Component({
@Options({
components: {
ActorCard,
},

View File

@ -118,9 +118,9 @@
</template>
<script lang="ts">
import { CONTACT } from "@/graphql/config";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
@Component({
@Options({
apollo: {
config: {
query: CONTACT,

View File

@ -59,14 +59,14 @@
</div>
</template>
<script lang="ts">
import { Component, Mixins, Prop, Watch } from "vue-property-decorator";
import { Options, mixins, Prop, Watch } from "vue-property-decorator";
import { Address, IAddress } from "../../types/address.model";
import AddressAutoCompleteMixin from "@/mixins/AddressAutoCompleteMixin";
@Component({
@Options({
inheritAttrs: false,
})
export default class AddressAutoComplete extends Mixins(
export default class AddressAutoComplete extends mixins(
AddressAutoCompleteMixin
) {
@Prop({ required: false, default: false }) type!: string | false;

View File

@ -1,16 +1,3 @@
<docs>
### Example
```vue
<DateCalendarIcon date="2019-10-05T18:41:11.720Z" />
```
```vue
<DateCalendarIcon
:date="new Date()"
/>
```
</docs>
<template>
<div
class="datetime-container"
@ -25,9 +12,8 @@
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class DateCalendarIcon extends Vue {
/**
* `date` can be a string or an actual date object.
@ -36,7 +22,7 @@ export default class DateCalendarIcon extends Vue {
@Prop({ required: false, default: false }) small!: boolean;
get dateObj(): Date {
return new Date(this.$props.date);
return new Date(this.date);
}
get month(): string {

View File

@ -6,10 +6,10 @@
<script lang="ts">
import { IMedia } from "@/types/media.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import LazyImageWrapper from "../Image/LazyImageWrapper.vue";
@Component({
@Options({
components: {
LazyImageWrapper,
},

View File

@ -92,7 +92,7 @@ import {
organizerDisplayName,
organizer,
} from "@/types/event.model";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import { Actor, Person } from "@/types/actor";
@ -100,7 +100,7 @@ import { EventStatus, ParticipantRole } from "@/types/enums";
import RouteName from "../../router/name";
import InlineAddress from "@/components/Address/InlineAddress.vue";
@Component({
@Options({
components: {
DateCalendarIcon,
LazyImageWrapper,

View File

@ -1,22 +1,3 @@
<docs>
#### Give a translated and localized text that give the starting and ending datetime for an event.
##### Start date with no ending
```vue
<EventFullDate beginsOn="2015-10-06T18:41:11.720Z" />
```
##### Start date with an ending the same day
```vue
<EventFullDate beginsOn="2015-10-06T18:41:11.720Z" endsOn="2015-10-06T20:41:11.720Z" />
```
##### Start date with an ending on a different day
```vue
<EventFullDate beginsOn="2015-10-06T18:41:11.720Z" endsOn="2032-10-06T18:41:11.720Z" />
```
</docs>
<template>
<p v-if="!endsOn">
<span>{{
@ -112,9 +93,8 @@
</template>
<script lang="ts">
import { getTimezoneOffset } from "date-fns-tz";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class EventFullDate extends Vue {
@Prop({ required: true }) beginsOn!: string;

View File

@ -75,7 +75,7 @@
<script lang="ts">
import { IEventCardOptions, IEvent } from "@/types/event.model";
import { Component, Prop } from "vue-property-decorator";
import { Options, Prop } from "vue-property-decorator";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import { IPerson, usernameWithDomain } from "@/types/actor";
import { mixins } from "vue-class-component";
@ -93,7 +93,7 @@ const defaultOptions: IEventCardOptions = {
memberofGroup: false,
};
@Component({
@Options({
components: {
DateCalendarIcon,
},

View File

@ -70,7 +70,7 @@
import { Address, IAddress } from "@/types/address.model";
import { RoutingTransportationType, RoutingType } from "@/types/enums";
import { PropType } from "vue";
import { Component, Vue, Prop } from "vue-property-decorator";
import { Options, Vue, Prop } from "vue-property-decorator";
const RoutingParamType = {
[RoutingType.OPENSTREETMAP]: {
@ -87,7 +87,7 @@ const RoutingParamType = {
},
};
@Component({
@Options({
components: {
"map-leaflet": () =>
import(/* webpackChunkName: "map" */ "../../components/Map.vue"),

View File

@ -21,9 +21,8 @@
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class EventMetadataBlock extends Vue {
@Prop({ required: false, type: String }) icon!: string;

View File

@ -83,9 +83,8 @@
import { EventMetadataKeyType, EventMetadataType } from "@/types/enums";
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Ref, Vue } from "vue-property-decorator";
import { Prop, Ref, Vue } from "vue-property-decorator";
@Component
export default class EventMetadataItem extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
value!: IEventMetadataDescription;

View File

@ -99,7 +99,7 @@ import {
} from "@/types/event-metadata";
import cloneDeep from "lodash/cloneDeep";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import EventMetadataItem from "./EventMetadataItem.vue";
import { eventMetaDataList } from "../../services/EventMetadata";
import { EventMetadataCategories, EventMetadataType } from "@/types/enums";
@ -109,7 +109,7 @@ type GroupedIEventMetadata = Array<{
items: IEventMetadata[];
}>;
@Component({
@Options({
components: {
EventMetadataItem,
},

View File

@ -141,7 +141,7 @@ import { IConfig } from "@/types/config.model";
import { EventMetadataKeyType, EventMetadataType } from "@/types/enums";
import { IEvent } from "@/types/event.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import RouteName from "../../router/name";
import { usernameWithDomain } from "../../types/actor";
import EventMetadataBlock from "./EventMetadataBlock.vue";
@ -156,7 +156,7 @@ import {
import { eventMetaDataList } from "../../services/EventMetadata";
import { IUser } from "@/types/current-user.model";
@Component({
@Options({
components: {
EventMetadataBlock,
EventFullDate,

View File

@ -102,7 +102,7 @@
</router-link>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { IEvent, organizer, organizerDisplayName } from "@/types/event.model";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import { ParticipantRole } from "@/types/enums";
@ -110,7 +110,7 @@ import RouteName from "../../router/name";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import InlineAddress from "@/components/Address/InlineAddress.vue";
@Component({
@Options({
components: {
DateCalendarIcon,
LazyImageWrapper,
@ -134,7 +134,7 @@ export default class EventMinimalistCard extends Vue {
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@use "@/styles/_event-card";
@import "~bulma/sass/utilities/mixins.sass";
@import "../node_modules/bulma/sass/utilities/mixins.sass";
@import "@/variables.scss";
.event-minimalist-card-wrapper {

View File

@ -253,7 +253,7 @@
</template>
<script lang="ts">
import { Component, Prop } from "vue-property-decorator";
import { Options, Prop } from "vue-property-decorator";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import { mixins } from "vue-class-component";
import { RawLocation, Route } from "vue-router";
@ -283,7 +283,7 @@ const defaultOptions: IEventCardOptions = {
memberofGroup: false,
};
@Component({
@Options({
components: {
DateCalendarIcon,
PopoverActorCard,
@ -349,7 +349,7 @@ export default class EventParticipationCard extends mixins(
) {
const organizerActor = participation.event.organizerActor as IPerson;
await changeIdentity(this.$apollo.provider.defaultClient, organizerActor);
this.$buefy.notification.open({
this.$oruga.notification.open({
message: this.$t(
"Current identity has been changed to {identityName} in order to manage this event.",
{
@ -396,7 +396,7 @@ export default class EventParticipationCard extends mixins(
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@use "@/styles/_event-card";
@import "~bulma/sass/utilities/mixins.sass";
@import "../node_modules/bulma/sass/utilities/mixins.sass";
article.box {
div.tag-container {

View File

@ -7,7 +7,7 @@
:message="fieldErrors"
:type="{ 'is-danger': fieldErrors.length }"
>
<template slot="label">
<template #label>
{{ actualLabel }}
<span
class="is-size-6 has-text-weight-normal"
@ -103,19 +103,19 @@
</div>
</template>
<script lang="ts">
import { Component, Prop, Watch, Mixins } from "vue-property-decorator";
import { Options, Prop, Watch, mixins } from "vue-property-decorator";
import { LatLng } from "leaflet";
import { Address, IAddress } from "../../types/address.model";
import AddressAutoCompleteMixin from "../../mixins/AddressAutoCompleteMixin";
import AddressInfo from "../../components/Address/AddressInfo.vue";
@Component({
@Options({
inheritAttrs: false,
components: {
AddressInfo,
},
})
export default class FullAddressAutoComplete extends Mixins(
export default class FullAddressAutoComplete extends mixins(
AddressAutoCompleteMixin
) {
@Prop({ required: false, default: "" }) label!: string;

View File

@ -17,10 +17,10 @@
<script lang="ts">
import { IEvent } from "@/types/event.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import EventMinimalistCard from "./EventMinimalistCard.vue";
@Component({
@Options({
components: {
EventMinimalistCard,
},

View File

@ -12,9 +12,8 @@
<script lang="ts">
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class EtherpadIntegration extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
metadata!: IEventMetadataDescription;

View File

@ -12,9 +12,8 @@
<script lang="ts">
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class JitsiMeetIntegration extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
metadata!: IEventMetadataDescription;

View File

@ -15,9 +15,8 @@
<script lang="ts">
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class PeerTubeIntegration extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
metadata!: IEventMetadataDescription;

View File

@ -16,9 +16,8 @@
<script lang="ts">
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class TwitchIntegration extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
metadata!: IEventMetadataDescription;

View File

@ -16,9 +16,8 @@
<script lang="ts">
import { IEventMetadataDescription } from "@/types/event-metadata";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
@Component
export default class YouTubeIntegration extends Vue {
@Prop({ type: Object as PropType<IEventMetadataDescription>, required: true })
metadata!: IEventMetadataDescription;

View File

@ -11,9 +11,9 @@
<script lang="ts">
import { IEvent } from "@/types/event.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import EventCard from "./EventCard.vue";
@Component({
@Options({
components: {
EventCard,
},

View File

@ -12,10 +12,10 @@
<script lang="ts">
import { IEvent } from "@/types/event.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import EventMinimalistCard from "./EventMinimalistCard.vue";
@Component({
@Options({
components: {
EventMinimalistCard,
},

View File

@ -35,7 +35,7 @@
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { IPerson, IActor, Actor } from "@/types/actor";
import {
CURRENT_ACTOR_CLIENT,
@ -46,7 +46,7 @@ import { Paginate } from "@/types/paginate";
import { IMember } from "@/types/actor/member.model";
import { MemberRole } from "@/types/enums";
@Component({
@Options({
apollo: {
groupMemberships: {
query: LOGGED_USER_MEMBERSHIPS,

View File

@ -118,7 +118,7 @@
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { Options, Prop, Vue, Watch } from "vue-property-decorator";
import { IMember } from "@/types/actor/member.model";
import { IActor, IGroup, IPerson, usernameWithDomain } from "../../types/actor";
import OrganizerPicker from "./OrganizerPicker.vue";
@ -138,7 +138,7 @@ const MEMBER_ROLES = [
MemberRole.MEMBER,
];
@Component({
@Options({
components: { OrganizerPicker },
apollo: {
members: {

View File

@ -1,28 +1,3 @@
import {EventJoinOptions} from "@/types/event.model";
<docs>
A button to set your participation
##### If the participant has been confirmed
```vue
<ParticipationButton :participation="{ role: 'PARTICIPANT' }" :currentActor="{ preferredUsername: 'test', avatar: { url: 'https://huit.re/EPX7vs1j' } }" />
```
##### If the participant has not being approved yet
```vue
<ParticipationButton :participation="{ role: 'NOT_APPROVED' }" :currentActor="{ preferredUsername: 'test', avatar: { url: 'https://huit.re/EPX7vs1j' } }" />
```
##### If the participant has been rejected
```vue
<ParticipationButton :participation="{ role: 'REJECTED' }" :currentActor="{ preferredUsername: 'test', avatar: { url: 'https://huit.re/EPX7vs1j' } }" />
```
##### If the participant doesn't exist yet
```vue
<ParticipationButton :participation="null" :currentActor="{ preferredUsername: 'test', avatar: { url: 'https://huit.re/EPX7vs1j' } }" />
```
</docs>
<template>
<div class="participation-button">
<o-dropdown
@ -61,13 +36,15 @@ A button to set your participation
position="is-bottom-left"
class="dropdown-disabled"
>
<button class="button is-success is-large" type="button" slot="trigger">
<o-icon icon="timer-sand-empty" />
<template>
<span>{{ $t("I participate") }}</span>
</template>
<o-icon icon="menu-down" />
</button>
<template #trigger>
<button class="button is-success is-large" type="button">
<o-icon icon="timer-sand-empty" />
<template>
<span>{{ $t("I participate") }}</span>
</template>
<o-icon icon="menu-down" />
</button>
</template>
<!-- <o-dropdown-item :value="false" aria-role="listitem">-->
<!-- {{ $t('Change my identity…')}}-->
@ -180,7 +157,7 @@ A button to set your participation
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { EventJoinOptions, ParticipantRole } from "@/types/enums";
import { IParticipant } from "../../types/participant.model";
import { IEvent } from "../../types/event.model";
@ -191,7 +168,7 @@ import { CONFIG } from "../../graphql/config";
import { IConfig } from "../../types/config.model";
import RouteName from "../../router/name";
@Component({
@Options({
apollo: {
currentUser: {
query: CURRENT_USER_CLIENT,

View File

@ -14,10 +14,10 @@
<script lang="ts">
import { IEvent } from "@/types/event.model";
import { formatDistanceToNow } from "date-fns";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import EventCard from "./EventCard.vue";
@Component({
@Options({
components: {
EventCard,
},

View File

@ -123,14 +123,14 @@
</template>
<script lang="ts">
import { Component, Prop, Vue, Ref } from "vue-property-decorator";
import { Options, Prop, Vue, Ref } from "vue-property-decorator";
import { EventStatus, EventVisibility } from "@/types/enums";
import { IEvent } from "../../types/event.model";
import DiasporaLogo from "../Share/DiasporaLogo.vue";
import MastodonLogo from "../Share/MastodonLogo.vue";
import TelegramLogo from "../Share/TelegramLogo.vue";
@Component({
@Options({
components: {
DiasporaLogo,
MastodonLogo,

View File

@ -29,12 +29,12 @@
</o-field>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import differenceBy from "lodash/differenceBy";
import { ITag } from "../../types/tag.model";
import { FILTER_TAGS } from "@/graphql/tags";
@Component({
@Options({
apollo: {
tags: {
query: FILTER_TAGS,

View File

@ -81,17 +81,16 @@
</footer>
</template>
<script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator";
import { Vue, Watch } from "vue-property-decorator";
import { saveLocaleData } from "@/utils/auth";
import { loadLanguageAsync } from "@/utils/i18n";
import { i18n, loadLanguageAsync } from "@/utils/i18n";
import RouteName from "../router/name";
import langs from "../i18n/langs.json";
@Component
export default class Footer extends Vue {
RouteName = RouteName;
locale: string | null = this.$i18n.locale;
locale: string | null = i18n.global.locale;
langs: Record<string, string> = langs;
@ -123,7 +122,7 @@ export default class Footer extends Vue {
}
</script>
<style lang="scss" scoped>
@import "~bulma/sass/utilities/mixins.sass";
@import "../node_modules/bulma/sass/utilities/mixins.sass";
footer.footer {
color: $secondary;
display: flex;

View File

@ -56,13 +56,13 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { displayName, IGroup, usernameWithDomain } from "@/types/actor";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import RouteName from "../../router/name";
import InlineAddress from "@/components/Address/InlineAddress.vue";
@Component({
@Options({
components: {
LazyImageWrapper,
InlineAddress,

View File

@ -50,7 +50,9 @@
</div>
<div>
<o-dropdown aria-role="list" position="is-bottom-left">
<o-icon icon="dots-horizontal" slot="trigger" />
<template #trigger>
<o-icon icon="dots-horizontal" />
</template>
<o-dropdown-item aria-role="listitem" @click="$emit('leave')">
<o-icon icon="exit-to-app" />
@ -63,13 +65,12 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
import { displayNameAndUsername, usernameWithDomain } from "@/types/actor";
import { IMember } from "@/types/actor/member.model";
import { MemberRole } from "@/types/enums";
import RouteName from "../../router/name";
@Component
export default class GroupMemberCard extends Vue {
@Prop({ required: true }) member!: IMember;

View File

@ -17,10 +17,9 @@
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { Route } from "vue-router";
import { Vue, Prop } from "vue-property-decorator";
import { RouteLocationNormalizedLoaded } from "vue-router";
@Component
export default class GroupSection extends Vue {
@Prop({ required: true, type: String }) title!: string;
@ -29,7 +28,7 @@ export default class GroupSection extends Vue {
@Prop({ required: false, type: Boolean, default: true })
privateSection!: boolean;
@Prop({ required: true, type: Object }) route!: Route;
@Prop({ required: true, type: Object }) route!: RouteLocationNormalizedLoaded;
}
</script>
<style lang="scss" scoped>

View File

@ -70,12 +70,11 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
import { usernameWithDomain } from "@/types/actor";
import { IMember } from "@/types/actor/member.model";
import RouteName from "../../router/name";
@Component
export default class InvitationCard extends Vue {
@Prop({ required: true }) member!: IMember;

View File

@ -11,13 +11,13 @@
</template>
<script lang="ts">
import { ACCEPT_INVITATION, REJECT_INVITATION } from "@/graphql/member";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import InvitationCard from "@/components/Group/InvitationCard.vue";
import { PERSON_STATUS_GROUP } from "@/graphql/actor";
import { IMember } from "@/types/actor/member.model";
import { IGroup, IPerson, usernameWithDomain } from "@/types/actor";
@Component({
@Options({
components: {
InvitationCard,
},

View File

@ -7,12 +7,12 @@
/>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue";
import { FETCH_GROUP } from "@/graphql/group";
import { IGroup } from "@/types/actor";
@Component({
@Options({
components: { RedirectWithAccount },
apollo: {
group: {

View File

@ -11,9 +11,9 @@
<script lang="ts">
import { IGroup } from "@/types/actor";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import GroupCard from "./GroupCard.vue";
@Component({
@Options({
components: {
GroupCard,
},

View File

@ -113,14 +113,14 @@
</template>
<script lang="ts">
import { Component, Prop, Vue, Ref } from "vue-property-decorator";
import { Options, Prop, Vue, Ref } from "vue-property-decorator";
import { GroupVisibility } from "@/types/enums";
import DiasporaLogo from "../Share/DiasporaLogo.vue";
import MastodonLogo from "../Share/MastodonLogo.vue";
import TelegramLogo from "../Share/MastodonLogo.vue";
import { displayName, IGroup } from "@/types/actor";
@Component({
@Options({
components: {
DiasporaLogo,
MastodonLogo,

View File

@ -4,9 +4,8 @@
<script lang="ts">
import { decode } from "blurhash";
import { Component, Prop, Ref, Vue } from "vue-property-decorator";
import { Prop, Ref, Vue } from "vue-property-decorator";
@Component
export default class BlurhashImg extends Vue {
@Prop({ type: String, required: true }) hash!: string;
@Prop({ type: Number, default: 1 }) aspectRatio!: string;

View File

@ -25,10 +25,10 @@
</template>
<script lang="ts">
import { Prop, Component, Vue, Ref, Watch } from "vue-property-decorator";
import { Prop, Options, Vue, Ref, Watch } from "vue-property-decorator";
import BlurhashImg from "./BlurhashImg.vue";
@Component({
@Options({
components: {
BlurhashImg,
},

View File

@ -11,7 +11,7 @@
<script lang="ts">
import { IMedia } from "@/types/media.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import LazyImage from "../Image/LazyImage.vue";
const DEFAULT_CARD_URL = "/img/mobilizon_default_card.png";
@ -27,7 +27,7 @@ const DEFAULT_PICTURE = {
},
};
@Component({
@Options({
components: {
LazyImage,
},

View File

@ -28,13 +28,19 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { defineComponent } from "vue";
@Component
export default class Logo extends Vue {
@Prop({ type: Boolean, required: false, default: false }) invert!: boolean;
}
export default defineComponent({
props: {
invert: {
type: Boolean,
required: false,
default: false,
},
},
});
</script>
<style lang="scss" scoped>
svg {
fill: $background-color;

View File

@ -19,10 +19,10 @@
:zoomInTitle="$t('Zoom in')"
:zoomOutTitle="$t('Zoom out')"
></l-control-zoom>
<v-locatecontrol
<!-- <v-locatecontrol
v-if="canDoGeoLocation"
:options="{ icon: 'mdi mdi-map-marker' }"
/>
/> -->
<l-marker
:lat-lng="[lat, lon]"
@add="openPopup"
@ -42,7 +42,7 @@
<script lang="ts">
import { Icon, LatLng, LeafletMouseEvent, LeafletEvent } from "leaflet";
import "leaflet/dist/leaflet.css";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import {
LMap,
LTileLayer,
@ -50,12 +50,12 @@ import {
LPopup,
LIcon,
LControlZoom,
} from "vue2-leaflet";
import Vue2LeafletLocateControl from "@/components/Map/Vue2LeafletLocateControl.vue";
} from "@vue-leaflet/vue-leaflet";
// import Vue2LeafletLocateControl from "@/components/Map/Vue2LeafletLocateControl.vue";
import { CONFIG } from "../graphql/config";
import { IConfig } from "../types/config.model";
@Component({
@Options({
components: {
LTileLayer,
LMap,
@ -63,7 +63,7 @@ import { IConfig } from "../types/config.model";
LPopup,
LIcon,
LControlZoom,
"v-locatecontrol": Vue2LeafletLocateControl,
// "v-locatecontrol": Vue2LeafletLocateControl,
},
apollo: {
config: CONFIG,

View File

@ -11,11 +11,11 @@
*/
import { DomEvent } from "leaflet";
import { findRealParent, propsBinder } from "vue2-leaflet";
import { findRealParent, propsBinder } from "@vue-leaflet/vue-leaflet";
import Locatecontrol from "leaflet.locatecontrol";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
@Component({
@Options({
beforeDestroy() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore

View File

@ -0,0 +1,381 @@
<template>
<o-navbar
id="navbar"
type="is-secondary"
wrapper-class="container"
:active.sync="mobileNavbarActive"
>
<template #brand>
<o-navbar-item
tag="router-link"
:to="{ name: RouteName.HOME }"
:aria-label="$t('Home')"
>
<logo />
</o-navbar-item>
</template>
<template #start>
<o-navbar-item tag="router-link" :to="{ name: RouteName.SEARCH }">{{
$t("Explore")
}}</o-navbar-item>
<o-navbar-item
v-if="currentActor.id && currentUser.isLoggedIn"
tag="router-link"
:to="{ name: RouteName.MY_EVENTS }"
>{{ $t("My events") }}</o-navbar-item
>
<o-navbar-item
tag="router-link"
:to="{ name: RouteName.MY_GROUPS }"
v-if="
config &&
config.features.groups &&
currentActor.id &&
currentUser.isLoggedIn
"
>{{ $t("My groups") }}</o-navbar-item
>
<o-navbar-item
tag="span"
v-if="
config &&
config.features.eventCreation &&
currentActor.id &&
currentUser.isLoggedIn
"
>
<o-button
v-if="!hideCreateEventsButton"
tag="router-link"
:to="{ name: RouteName.CREATE_EVENT }"
type="is-primary"
>{{ $t("Create") }}</o-button
>
</o-navbar-item>
<o-navbar-item
v-if="config && config.features.koenaConnect"
class="koena"
tag="a"
href="https://mediation.koena.net/framasoft/mobilizon/"
target="_blank"
rel="noopener external"
hreflang="fr"
>
<img
src="/img/koena-a11y.svg"
width="150"
alt="Contact accessibilité"
/>
</o-navbar-item>
</template>
<template #end>
<o-navbar-item tag="div">
<search-field @navbar-search="mobileNavbarActive = false" />
</o-navbar-item>
<o-navbar-dropdown
v-if="currentActor.id && currentUser.isLoggedIn"
right
collapsible
ref="user-dropdown"
tabindex="0"
tag="span"
@keyup.enter="toggleMenu"
>
<template #label v-if="currentActor">
<div class="identity-wrapper">
<div>
<figure class="image is-32x32" v-if="currentActor.avatar">
<img
class="is-rounded"
alt="avatarUrl"
:src="currentActor.avatar.url"
/>
</figure>
<o-icon v-else icon="account-circle" />
</div>
<div class="media-content is-hidden-desktop">
<span>{{ displayName(currentActor) }}</span>
<span class="has-text-grey-dark" v-if="currentActor.name"
>@{{ currentActor.preferredUsername }}</span
>
</div>
</div>
</template>
<!-- No identities dropdown if no identities -->
<span v-if="identities.length <= 1" />
<o-navbar-item
tag="span"
v-for="identity in identities"
v-else
:active="identity.id === currentActor.id"
:key="identity.id"
tabindex="0"
@click="setIdentity(identity)"
@keyup.enter="setIdentity(identity)"
>
<span>
<div class="media-left">
<figure class="image is-32x32" v-if="identity.avatar">
<img
class="is-rounded"
loading="lazy"
:src="identity.avatar.url"
alt
/>
</figure>
<o-icon v-else size="is-medium" icon="account-circle" />
</div>
<div class="media-content">
<span>{{ displayName(identity) }}</span>
<span class="has-text-grey-dark" v-if="identity.name"
>@{{ identity.preferredUsername }}</span
>
</div>
</span>
<hr class="navbar-divider" role="presentation" />
</o-navbar-item>
<o-navbar-item
tag="router-link"
:to="{ name: RouteName.UPDATE_IDENTITY }"
>{{ $t("My account") }}</o-navbar-item
>
<o-navbar-item
v-if="currentUser.role === ICurrentUserRole.ADMINISTRATOR"
tag="router-link"
:to="{ name: RouteName.ADMIN_DASHBOARD }"
>{{ $t("Administration") }}</o-navbar-item
>
<o-navbar-item
tag="span"
tabindex="0"
@click="logout"
@keyup.enter="logout"
>
<span>{{ $t("Log out") }}</span>
</o-navbar-item>
</o-navbar-dropdown>
<o-navbar-item v-else tag="div">
<div class="buttons">
<router-link
class="button is-primary"
v-if="config && config.registrationsOpen"
:to="{ name: RouteName.REGISTER }"
>
<strong>{{ $t("Sign up") }}</strong>
</router-link>
<router-link
class="button is-light"
:to="{ name: RouteName.LOGIN }"
>{{ $t("Log in") }}</router-link
>
</div>
</o-navbar-item>
</template>
</o-navbar>
</template>
<script lang="ts">
import Logo from "@/components/Logo.vue";
import { GraphQLError } from "graphql";
import { loadLanguageAsync } from "@/utils/i18n";
import { CURRENT_USER_CLIENT, USER_SETTINGS } from "../graphql/user";
import { changeIdentity, logout } from "../utils/auth";
import {
CURRENT_ACTOR_CLIENT,
IDENTITIES,
UPDATE_DEFAULT_ACTOR,
} from "../graphql/actor";
import { IPerson, Person } from "../types/actor";
import { CONFIG } from "../graphql/config";
import { IConfig } from "../types/config.model";
import { ICurrentUser, IUser } from "../types/current-user.model";
import SearchField from "./SearchField.vue";
import RouteName from "../router/name";
import { defineComponent } from "vue-demi";
import { useMutation, useQuery, useResult } from "@vue/apollo-composable";
import { ref } from "vue";
export default defineComponent({
name: "NavBar",
setup() {
const { result: resultCurrentUser } = useQuery(CURRENT_USER_CLIENT);
const currentUser =
useResult<{ currentUser: ICurrentUser }>(resultCurrentUser);
const { result: resultCurrentActor } = useQuery(CURRENT_ACTOR_CLIENT);
const currentActor =
useResult<{ currentUser: IPerson }>(resultCurrentActor);
const { mutate: updateDefaultActor } = useMutation(UPDATE_DEFAULT_ACTOR);
const { result: configResult } = useQuery(CONFIG);
const config = useResult<{ config: IConfig }>(configResult);
const { result: loggedUserResult } = useQuery(USER_SETTINGS);
const loggedUser = useResult<{ config: IUser }>(loggedUserResult);
const { result: identitiesResult } = useQuery(IDENTITIES);
const identities = useResult<{ identities: IPerson[] }, [], Person[]>(
identitiesResult,
[],
(data) => data.identities.map((identity: IPerson) => new Person(identity))
);
const mobileNavbarActive = false;
const userDropDown = ref(null) as any;
return {
currentUser,
currentActor,
config,
loggedUser,
identities,
mobileNavbarActive,
userDropDown,
updateDefaultActor,
};
},
components: {
Logo,
SearchField,
},
methods: {
toggleMenu(): void {
console.debug("called toggleMenu");
if (this.userDropDown) {
this.userDropDown.showMenu();
}
},
async handleErrors(errors: GraphQLError[]): Promise<void> {
if (
errors.length > 0 &&
errors[0].message ===
"You need to be logged-in to view your list of identities"
) {
await this.logout();
}
},
async logout(): Promise<void> {
await logout(this.$apollo.provider.defaultClient);
this.$oruga.notification.open({
message: this.$t("You have been disconnected") as string,
type: "is-success",
position: "is-bottom-right",
duration: 5000,
});
if (this.$route.name === RouteName.HOME) return;
await this.$router.push({ name: RouteName.HOME });
},
async setIdentity(identity: IPerson): Promise<void> {
await this.updateDefaultActor({
preferredUsername: identity.preferredUsername,
});
return changeIdentity(this.$apollo.provider.defaultClient, identity);
},
},
watch: {
async currentActor(): Promise<void> {
if (!this.currentUser?.isLoggedIn) return;
// If we don't have any identities, the user has validated their account,
// is logging for the first time but didn't create an identity somehow
if (this.identities?.length === 0) {
try {
await this.$router.push({
name: RouteName.REGISTER_PROFILE,
params: {
email: this.currentUser.email,
userAlreadyActivated: "true",
},
});
} catch (err) {
return undefined;
}
}
},
loggedUser(): void {
if (this.loggedUser?.locale) {
console.debug("Setting locale from navbar");
loadLanguageAsync(this.loggedUser.locale);
}
},
},
computed: {
hideCreateEventsButton(): boolean {
return !!this.config?.restrictions?.onlyGroupsCanCreateEvents;
},
},
});
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
nav {
.navbar-item {
a.button {
font-weight: bold;
}
svg {
height: 1.75rem;
}
}
.navbar-dropdown .navbar-item {
cursor: pointer;
span {
display: flex;
}
&.is-active {
background: $secondary;
}
span.icon.is-medium {
display: flex;
}
img {
max-height: 2.5em;
}
}
.navbar-item.has-dropdown a.navbar-link figure {
@include margin-right(0.75rem);
display: flex;
align-items: center;
}
a.navbar-item:focus-within {
background-color: inherit;
}
.koena {
padding-top: 0;
padding-bottom: 0;
& > img {
max-height: 4rem;
padding-top: 0.2rem;
}
}
.identity-wrapper {
display: flex;
.media-content span {
display: flex;
color: $violet-2;
}
}
}
</style>

View File

@ -1,185 +1,426 @@
<template>
<o-navbar
id="navbar"
type="is-secondary"
wrapper-class="container"
:active.sync="mobileNavbarActive"
>
<template #brand>
<o-navbar-item
tag="router-link"
:to="{ name: RouteName.HOME }"
:aria-label="$t('Home')"
>
<logo />
</o-navbar-item>
</template>
<template #start>
<o-navbar-item tag="router-link" :to="{ name: RouteName.SEARCH }">{{
$t("Explore")
}}</o-navbar-item>
<o-navbar-item
v-if="currentActor.id && currentUser.isLoggedIn"
tag="router-link"
:to="{ name: RouteName.MY_EVENTS }"
>{{ $t("My events") }}</o-navbar-item
>
<o-navbar-item
tag="router-link"
:to="{ name: RouteName.MY_GROUPS }"
v-if="
config &&
config.features.groups &&
currentActor.id &&
currentUser.isLoggedIn
"
>{{ $t("My groups") }}</o-navbar-item
>
<o-navbar-item
tag="span"
v-if="
config &&
config.features.eventCreation &&
currentActor.id &&
currentUser.isLoggedIn
"
>
<o-button
v-if="!hideCreateEventsButton"
tag="router-link"
:to="{ name: RouteName.CREATE_EVENT }"
type="is-primary"
>{{ $t("Create") }}</o-button
>
</o-navbar-item>
<o-navbar-item
v-if="config && config.features.koenaConnect"
class="koena"
tag="a"
href="https://mediation.koena.net/framasoft/mobilizon/"
target="_blank"
rel="noopener external"
hreflang="fr"
>
<img
src="/img/koena-a11y.svg"
width="150"
alt="Contact accessibilité"
/>
</o-navbar-item>
</template>
<template #end>
<o-navbar-item tag="div">
<search-field @navbar-search="mobileNavbarActive = false" />
</o-navbar-item>
<nav class="bg-yellow-100 z-50">
<div class="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
<div class="relative flex items-center justify-between h-16">
<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
<!-- Mobile menu button-->
<button
type="button"
class="
inline-flex
items-center
justify-center
p-2
rounded-md
text-gray-400
hover:text-white hover:bg-yellow-400
focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white
"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="sr-only">{{ $t("Open main menu") }}</span>
<!--
Icon when menu is closed.
<o-navbar-dropdown
v-if="currentActor.id && currentUser.isLoggedIn"
right
collapsible
ref="user-dropdown"
tabindex="0"
tag="span"
@keyup.enter="toggleMenu"
>
<template #label v-if="currentActor">
<div class="identity-wrapper">
<div>
<figure class="image is-32x32" v-if="currentActor.avatar">
<img
class="is-rounded"
alt="avatarUrl"
:src="currentActor.avatar.url"
/>
</figure>
<o-icon v-else icon="account-circle" />
</div>
<div class="media-content is-hidden-desktop">
<span>{{ displayName(currentActor) }}</span>
<span class="has-text-grey-dark" v-if="currentActor.name"
>@{{ currentActor.preferredUsername }}</span
Heroicon name: outline/menu
Menu open: "hidden", Menu closed: "block"
-->
<svg
class="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<!--
Icon when menu is open.
Heroicon name: outline/x
Menu open: "block", Menu closed: "hidden"
-->
<svg
class="hidden h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
<div
class="
flex-1 flex
items-center
justify-center
sm:items-stretch sm:justify-start
"
>
<div class="flex-shrink-0 flex items-center">
<img
class="block lg:hidden h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-mark-indigo-500.svg"
alt="Workflow"
/>
<router-link :to="{ name: RouteName.HOME }">
<logo class="hidden lg:block h-8 w-auto" />
</router-link>
</div>
<div class="hidden sm:block sm:ml-6">
<div class="flex items-center space-x-4">
<router-link
:to="{ name: RouteName.SEARCH }"
class="
text-gray-700
hover:bg-yellow-400 hover:text-black
px-3
py-2
rounded-md
text-sm
font-medium
"
>{{ $t("Explore") }}</router-link
>
<router-link
:to="{ name: RouteName.MY_EVENTS }"
v-if="currentActor.id && currentUser.isLoggedIn"
class="
text-gray-700
hover:bg-yellow-400 hover:text-black
px-3
py-2
rounded-md
text-sm
font-medium
"
>{{ $t("My events") }}</router-link
>
<router-link
:to="{ name: RouteName.MY_GROUPS }"
v-if="
config &&
config.features.groups &&
currentActor.id &&
currentUser.isLoggedIn
"
class="
text-gray-700
hover:bg-yellow-400 hover:text-black
px-3
py-2
rounded-md
text-sm
font-medium
"
>{{ $t("My groups") }}</router-link
>
<router-link
v-if="!hideCreateEventsButton"
:to="{ name: RouteName.CREATE_EVENT }"
class="
p-2
pl-5
pr-5
transition-colors
duration-700
transform
bg-indigo-500
hover:bg-blue-400
text-gray-100
rounded-lg
focus:border-4
border-indigo-300
"
>
{{ $t("Create") }}
</router-link>
<a
v-if="config && config.features.koenaConnect"
class="koena"
href="https://mediation.koena.net/framasoft/mobilizon/"
target="_blank"
rel="noopener external"
hreflang="fr"
>
<img
src="/img/koena-a11y.svg"
width="150"
alt="Contact accessibilité"
/>
</a>
</div>
</div>
</template>
<!-- No identities dropdown if no identities -->
<span v-if="identities.length <= 1" />
<o-navbar-item
tag="span"
v-for="identity in identities"
v-else
:active="identity.id === currentActor.id"
:key="identity.id"
tabindex="0"
@click="setIdentity(identity)"
@keyup.enter="setIdentity(identity)"
>
<span>
<div class="media-left">
<figure class="image is-32x32" v-if="identity.avatar">
<img
class="is-rounded"
loading="lazy"
:src="identity.avatar.url"
alt
/>
</figure>
<o-icon v-else size="is-medium" icon="account-circle" />
</div>
<div class="media-content">
<span>{{ displayName(identity) }}</span>
<span class="has-text-grey-dark" v-if="identity.name"
>@{{ identity.preferredUsername }}</span
>
</div>
</span>
<hr class="navbar-divider" role="presentation" />
</o-navbar-item>
<o-navbar-item
tag="router-link"
:to="{ name: RouteName.UPDATE_IDENTITY }"
>{{ $t("My account") }}</o-navbar-item
>
<o-navbar-item
v-if="currentUser.role === ICurrentUserRole.ADMINISTRATOR"
tag="router-link"
:to="{ name: RouteName.ADMIN_DASHBOARD }"
>{{ $t("Administration") }}</o-navbar-item
>
<o-navbar-item
tag="span"
tabindex="0"
@click="logout"
@keyup.enter="logout"
>
<span>{{ $t("Log out") }}</span>
</o-navbar-item>
</o-navbar-dropdown>
<o-navbar-item v-else tag="div">
<div class="buttons">
<router-link
class="button is-primary"
v-if="config && config.registrationsOpen"
:to="{ name: RouteName.REGISTER }"
>
<strong>{{ $t("Sign up") }}</strong>
</router-link>
<router-link
class="button is-light"
:to="{ name: RouteName.LOGIN }"
>{{ $t("Log in") }}</router-link
>
</div>
</o-navbar-item>
</template>
</o-navbar>
<div
class="
absolute
inset-y-0
right-0
flex
items-center
pr-2
sm:static sm:inset-auto sm:ml-6 sm:pr-0
"
>
<div>
<search-field @navbar-search="mobileNavbarActive = false" />
</div>
<!-- Profile dropdown -->
<div class="ml-3 relative">
<div>
<button
type="button"
class="
bg-gray-800
flex
text-sm
rounded-full
focus:outline-none
focus:ring-2
focus:ring-offset-2
focus:ring-offset-gray-800
focus:ring-white
"
id="user-menu-button"
aria-expanded="false"
aria-haspopup="true"
@click="toggleMenu"
>
<span class="sr-only">{{ $t("Open user menu") }}</span>
<img
v-if="currentActor.avatar"
class="h-8 w-8 rounded-full"
:src="currentActor.avatar.url"
alt=""
/>
<o-icon v-else icon="account-circle" />
</button>
</div>
<!--
Dropdown menu, show/hide based on menu state.
Entering: "transition ease-out duration-100"
From: "transform opacity-0 scale-95"
To: "transform opacity-100 scale-100"
Leaving: "transition ease-in duration-75"
From: "transform opacity-100 scale-100"
To: "transform opacity-0 scale-95"
-->
<div
v-show="userDropDownOpened"
class="
origin-top-right
absolute
right-0
mt-2
w-48
rounded-md
shadow-lg
py-1
bg-white
ring-1 ring-black ring-opacity-5
focus:outline-none
"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu-button"
tabindex="-1"
>
<button
v-for="identity in identities"
:active="identity.id === currentActor.id"
:key="identity.id"
@click="setIdentity(identity)"
@keyup.enter="setIdentity(identity)"
class="
block
px-4
py-2
text-sm text-gray-700
hover:bg-yellow-400 hover:text-black
w-full
"
role="menuitem"
tabindex="-1"
id="user-menu-item-2"
>
<div class="flex text-left">
<div>
<div
v-if="identity.avatar"
class="
rounded-full
h-8
w-8
bg-gray-500
flex
items-center
justify-center
overflow-hidden
"
>
<img :src="identity.avatar.url" alt="" />
</div>
<o-icon v-else size="is-medium" icon="account-circle" />
</div>
<div class="flex flex-col">
<span class="ml-2 font-bold text-sm">{{
displayName(identity)
}}</span>
<span
class="ml-2 text-sm text-gray-600 dark:text-gray-400"
v-if="identity.name"
>@{{ identity.preferredUsername }}</span
>
</div>
</div>
</button>
<!-- Active: "bg-gray-100", Not Active: "" -->
<router-link
:to="{ name: RouteName.UPDATE_IDENTITY }"
class="
block
px-4
py-2
text-sm text-gray-700
hover:bg-yellow-400 hover:text-black
"
role="menuitem"
tabindex="-1"
id="user-menu-item-0"
>{{ $t("My account") }}</router-link
>
<router-link
v-if="currentUser.role === ICurrentUserRole.ADMINISTRATOR"
:to="{ name: RouteName.ADMIN_DASHBOARD }"
class="
block
px-4
py-2
text-sm text-gray-700
hover:bg-yellow-400 hover:text-black
"
role="menuitem"
tabindex="-1"
id="user-menu-item-1"
>{{ $t("Administration") }}</router-link
>
<button
@click="logout"
@keyup.enter="logout"
class="
block
px-4
py-2
text-sm text-gray-700
hover:bg-yellow-400 hover:text-black
text-left
w-full
"
role="menuitem"
tabindex="-1"
id="user-menu-item-2"
>
{{ $t("Log out") }}
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Mobile menu, show/hide based on menu state. -->
<div class="sm:hidden" id="mobile-menu">
<div class="px-2 pt-2 pb-3 space-y-1">
<!-- Current: "bg-gray-900 text-white", Default: "text-gray-300 hover:bg-gray-700 hover:text-white" -->
<a
href="#"
class="
bg-gray-900
text-white
block
px-3
py-2
rounded-md
text-base
font-medium
"
aria-current="page"
>Dashboard</a
>
<a
href="#"
class="
text-gray-300
hover:bg-gray-700 hover:text-white
block
px-3
py-2
rounded-md
text-base
font-medium
"
>Team</a
>
<a
href="#"
class="
text-gray-300
hover:bg-gray-700 hover:text-white
block
px-3
py-2
rounded-md
text-base
font-medium
"
>Projects</a
>
<a
href="#"
class="
text-gray-300
hover:bg-gray-700 hover:text-white
block
px-3
py-2
rounded-md
text-base
font-medium
"
>Calendar</a
>
</div>
</div>
</nav>
</template>
<script lang="ts">
@ -193,15 +434,21 @@ import {
IDENTITIES,
UPDATE_DEFAULT_ACTOR,
} from "../graphql/actor";
import { IPerson, Person } from "../types/actor";
import { displayName, IPerson, Person } from "../types/actor";
import { CONFIG } from "../graphql/config";
import { IConfig } from "../types/config.model";
import { ICurrentUser, IUser } from "../types/current-user.model";
import SearchField from "./SearchField.vue";
import RouteName from "../router/name";
import { defineComponent } from "vue-demi";
import { useQuery, useResult } from "@vue/apollo-composable";
import {
useApolloClient,
useMutation,
useQuery,
useResult,
} from "@vue/apollo-composable";
import { ref } from "vue";
import { ICurrentUserRole } from "@/types/enums";
export default defineComponent({
name: "NavBar",
@ -214,6 +461,8 @@ export default defineComponent({
const currentActor =
useResult<{ currentUser: IPerson }>(resultCurrentActor);
const { mutate: updateDefaultActor } = useMutation(UPDATE_DEFAULT_ACTOR);
const { result: configResult } = useQuery(CONFIG);
const config = useResult<{ config: IConfig }>(configResult);
@ -229,7 +478,9 @@ export default defineComponent({
const mobileNavbarActive = false;
const userDropDown = ref(null) as any;
const userDropDownOpened = ref(false);
const { resolveClient } = useApolloClient();
return {
currentUser,
@ -238,7 +489,12 @@ export default defineComponent({
loggedUser,
identities,
mobileNavbarActive,
userDropDown,
userDropDownOpened,
updateDefaultActor,
RouteName,
displayName,
ICurrentUserRole,
resolveClient,
};
},
components: {
@ -247,10 +503,7 @@ export default defineComponent({
},
methods: {
toggleMenu(): void {
console.debug("called toggleMenu");
if (this.userDropDown) {
this.userDropDown.showMenu();
}
this.userDropDownOpened = !this.userDropDownOpened;
},
async handleErrors(errors: GraphQLError[]): Promise<void> {
if (
@ -262,52 +515,41 @@ export default defineComponent({
}
},
async logout(): Promise<void> {
await logout(this.$apollo.provider.defaultClient);
this.$oruga.notification.open({
message: this.$t("You have been disconnected") as string,
type: "is-success",
position: "is-bottom-right",
duration: 5000,
});
await logout(this.resolveClient());
// this.$oruga.notification.open({
// message: this.$t("You have been disconnected") as string,
// type: "is-success",
// position: "is-bottom-right",
// duration: 5000,
// });
if (this.$route.name === RouteName.HOME) return;
await this.$router.push({ name: RouteName.HOME });
},
async setIdentity(identity: IPerson): Promise<void> {
await this.$apollo.mutate({
mutation: UPDATE_DEFAULT_ACTOR,
variables: {
preferredUsername: identity.preferredUsername,
},
await this.updateDefaultActor({
preferredUsername: identity.preferredUsername,
});
return changeIdentity(this.$apollo.provider.defaultClient, identity);
return changeIdentity(this.resolveClient(), identity);
},
},
watch: {
async currentActor(): Promise<void> {
if (!this.currentUser?.isLoggedIn) return;
const { data } = await this.$apollo.query<{ identities: IPerson[] }>({
query: IDENTITIES,
});
if (data) {
this.identities = data.identities.map(
(identity: IPerson) => new Person(identity)
);
// If we don't have any identities, the user has validated their account,
// is logging for the first time but didn't create an identity somehow
if (this.identities.length === 0) {
try {
await this.$router.push({
name: RouteName.REGISTER_PROFILE,
params: {
email: this.currentUser.email,
userAlreadyActivated: "true",
},
});
} catch (err) {
return undefined;
}
// If we don't have any identities, the user has validated their account,
// is logging for the first time but didn't create an identity somehow
if (this.identities?.length === 0) {
try {
await this.$router.push({
name: RouteName.REGISTER_PROFILE,
params: {
email: this.currentUser.email,
userAlreadyActivated: "true",
},
});
} catch (err) {
return undefined;
}
}
},

View File

@ -64,14 +64,14 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { confirmLocalAnonymousParticipation } from "@/services/AnonymousParticipationStorage";
import { EventJoinOptions } from "@/types/enums";
import { IParticipant } from "../../types/participant.model";
import RouteName from "../../router/name";
import { CONFIRM_PARTICIPATION } from "../../graphql/event";
@Component({
@Options({
metaInfo() {
return {
title: this.$t("Confirm participation") as string,

View File

@ -120,7 +120,7 @@
<script lang="ts">
import { EventJoinOptions, EventStatus, ParticipantRole } from "@/types/enums";
import { IParticipant } from "@/types/participant.model";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import RouteName from "@/router/name";
import { IEvent } from "@/types/event.model";
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
@ -133,7 +133,7 @@ import {
} from "@/services/AnonymousParticipationStorage";
import ParticipationButton from "../Event/ParticipationButton.vue";
@Component({
@Options({
apollo: {
currentActor: CURRENT_ACTOR_CLIENT,
config: CONFIG,

View File

@ -7,12 +7,12 @@
/>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue";
import { FETCH_EVENT } from "@/graphql/event";
import { IEvent } from "@/types/event.model";
@Component({
@Options({
components: { RedirectWithAccount },
apollo: {
event: {

View File

@ -125,7 +125,7 @@
</section>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { EventModel, IEvent } from "@/types/event.model";
import { FETCH_EVENT_BASIC, JOIN_EVENT } from "@/graphql/event";
import { IConfig } from "@/types/config.model";
@ -136,7 +136,7 @@ import RouteName from "@/router/name";
import { IParticipant } from "../../types/participant.model";
import { ApolloCache, FetchResult } from "@apollo/client/core";
@Component({
@Options({
apollo: {
event: {
query: FETCH_EVENT_BASIC,

View File

@ -99,7 +99,7 @@
</section>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { FETCH_EVENT } from "@/graphql/event";
import EventListViewCard from "@/components/Event/EventListViewCard.vue";
import { EventModel, IEvent } from "@/types/event.model";
@ -109,7 +109,7 @@ import { IConfig } from "@/types/config.model";
import Subtitle from "@/components/Utils/Subtitle.vue";
import RouteName from "../../router/name";
@Component({
@Options({
components: {
VerticalDivider,
EventListViewCard,

View File

@ -112,9 +112,8 @@ figure.image {
<script lang="ts">
import { IMedia } from "@/types/media.model";
import { Component, Model, Prop, Vue, Watch } from "vue-property-decorator";
import { Model, Prop, Vue, Watch } from "vue-property-decorator";
@Component
export default class PictureUpload extends Vue {
@Model("change", { type: File }) readonly pictureFile!: File;

View File

@ -11,10 +11,10 @@
<script lang="ts">
import { IPost } from "@/types/post.model";
import { PropType } from "vue";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import PostListItem from "./PostListItem.vue";
@Component({
@Options({
components: {
PostListItem,
},

View File

@ -42,13 +42,13 @@
</template>
<script lang="ts">
import { formatDistanceToNow, subWeeks, isBefore } from "date-fns";
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import RouteName from "../../router/name";
import { IPost } from "../../types/post.model";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import { displayName } from "@/types/actor";
@Component({
@Options({
components: {
LazyImageWrapper,
},
@ -75,7 +75,7 @@ export default class PostListItem extends Vue {
</script>
<style lang="scss" scoped>
@use "@/styles/_mixins" as *;
@import "~bulma/sass/utilities/mixins.sass";
@import "../node_modules/bulma/sass/utilities/mixins.sass";
.post-minimalist-card-wrapper {
display: grid;

View File

@ -113,7 +113,7 @@
</template>
<script lang="ts">
import { Component, Prop, Vue, Ref } from "vue-property-decorator";
import { Options, Prop, Vue, Ref } from "vue-property-decorator";
import { PostVisibility } from "@/types/enums";
import { IPost } from "../../types/post.model";
import DiasporaLogo from "../Share/DiasporaLogo.vue";
@ -122,7 +122,7 @@ import TelegramLogo from "../Share/TelegramLogo.vue";
import { PropType } from "vue";
import RouteName from "@/router/name";
@Component({
@Options({
components: {
DiasporaLogo,
MastodonLogo,

View File

@ -1,8 +1,3 @@
<docs>
```vue
<report-card :report="{ reported: { name: 'Some bad guy', preferredUsername: 'kevin' }, reporter: { preferredUsername: 'somePerson34' }, reportContent: 'This is not good'}" />
```
</docs>
<template>
<div class="card" v-if="report">
<div class="card-content">
@ -42,11 +37,10 @@
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Prop, Vue } from "vue-property-decorator";
import { IReport } from "@/types/report.model";
import { ActorType } from "@/types/enums";
@Component
export default class ReportCard extends Vue {
@Prop({ required: true }) report!: IReport;

View File

@ -90,10 +90,10 @@
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { IComment } from "../../types/comment.model";
@Component({
@Options({
mounted() {
this.$data.isActive = true;
},

View File

@ -37,21 +37,21 @@
</div>
</template>
<script lang="ts">
import { Component, Mixins, Prop } from "vue-property-decorator";
import { Route } from "vue-router";
import { Options, mixins, Prop } from "vue-property-decorator";
import Draggable, { ChangeEvent } from "vuedraggable";
import { SnackbarProgrammatic as Snackbar } from "buefy";
// import { SnackbarProgrammatic as Snackbar } from "buefy";
import { IResource } from "../../types/resource";
import RouteName from "../../router/name";
import ResourceMixin from "../../mixins/resource";
import { IGroup, usernameWithDomain } from "../../types/actor";
import ResourceDropdown from "./ResourceDropdown.vue";
import { UPDATE_RESOURCE } from "../../graphql/resources";
import { NavigationFailure } from "vue-router";
@Component({
@Options({
components: { Draggable, ResourceDropdown },
})
export default class FolderItem extends Mixins(ResourceMixin) {
export default class FolderItem extends mixins(ResourceMixin) {
@Prop({ required: true, type: Object }) resource!: IResource;
@Prop({ required: true, type: Object }) group!: IGroup;
@ -72,7 +72,9 @@ export default class FolderItem extends Mixins(ResourceMixin) {
usernameWithDomain = usernameWithDomain;
async onChange(evt: ChangeEvent<IResource>): Promise<Route | undefined> {
async onChange(
evt: ChangeEvent<IResource>
): Promise<NavigationFailure | void | undefined> {
if (evt.added && evt.added.element) {
const movedResource = evt.added.element as IResource;
const updatedResource = await this.moveResource(movedResource);
@ -111,11 +113,11 @@ export default class FolderItem extends Mixins(ResourceMixin) {
}
return data.updateResource;
} catch (e: any) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
// Snackbar.open({
// message: e.message,
// type: "is-danger",
// position: "is-bottom",
// });
return undefined;
}
}

View File

@ -17,8 +17,7 @@
</o-dropdown>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { Vue } from "vue-property-decorator";
@Component
export default class ResourceDropdown extends Vue {}
</script>

View File

@ -53,11 +53,11 @@
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import { IResource, mapServiceTypeToIcon } from "@/types/resource";
import ResourceDropdown from "@/components/Resource/ResourceDropdown.vue";
@Component({
@Options({
components: { ResourceDropdown },
})
export default class ResourceItem extends Vue {

View File

@ -89,11 +89,11 @@
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { Options, Vue, Prop } from "vue-property-decorator";
import { GET_RESOURCE } from "../../graphql/resources";
import { IResource } from "../../types/resource";
@Component({
@Options({
apollo: {
resource: {
query: GET_RESOURCE,

View File

@ -1,42 +1,78 @@
<template>
<label for="navSearchField">
<span class="visually-hidden">{{ defaultPlaceHolder }}</span>
<o-input
custom-class="searchField"
id="navSearchField"
icon="magnify"
<div class="flex relative mx-auto w-full max-w-md">
<label for="navSearchField" class="sr-only">{{ defaultPlaceHolder }}</label>
<input
class="
border-2 border-primary
bg-red
transition
h-12
px-5
pr-16
rounded-md
focus:outline-none
w-full
text-black text-lg
"
type="search"
rounded
name="search"
id="navSearchField"
dir="auto"
:placeholder="defaultPlaceHolder"
v-model="search"
@keyup.native.enter="enter"
@keyup.enter="enter"
/>
</label>
<button type="submit" class="absolute right-2 top-3 mr-4">
<svg
class="text-black h-6 w-4 fill-current"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 56.966 56.966"
style="enable-background: new 0 0 56.966 56.966"
xml:space="preserve"
width="512px"
height="512px"
>
<path
d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23 s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92 c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17 s-17-7.626-17-17S14.61,6,23.984,6z"
/>
</svg>
</button>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { defineComponent } from "vue";
import { useI18n } from "vue-i18n";
import RouteName from "../router/name";
@Component
export default class SearchField extends Vue {
@Prop({ type: String, required: false }) placeholder!: string;
export default defineComponent({
props: {
placeholder: {
required: false,
type: String,
},
},
setup(props) {
const search = "";
const { t } = useI18n({ useScope: "global" });
search = "";
async enter(): Promise<void> {
this.$emit("navbar-search");
await this.$router.push({
name: RouteName.SEARCH,
query: { term: this.search },
});
}
get defaultPlaceHolder(): string {
// We can't use "this" inside @Prop's default value.
return this.placeholder || (this.$t("Search") as string);
}
}
const defaultPlaceHolder = props.placeholder || t("Search");
return { search, defaultPlaceHolder };
},
methods: {
async enter(): Promise<void> {
this.$emit("navbar-search");
await this.$router.push({
name: RouteName.SEARCH,
query: { term: this.search },
});
},
},
});
</script>
<style lang="scss">

View File

@ -40,12 +40,10 @@
</div>
</template>
<script lang="ts">
import { Component } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { mixins } from "vue-class-component";
// import { SnackbarProgrammatic as Snackbar } from "buefy";
import { mixins } from "vue-property-decorator";
import Onboarding from "../../mixins/onboarding";
@Component
export default class NotificationsOnboarding extends mixins(Onboarding) {
notificationOnDay = true;
@ -61,11 +59,11 @@ export default class NotificationsOnboarding extends mixins(Onboarding) {
try {
this.doUpdateSetting(variables);
} catch (e: any) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
// Snackbar.open({
// message: e.message,
// type: "is-danger",
// position: "is-bottom",
// });
}
}
}

View File

@ -7,14 +7,13 @@
</li>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Route } from "vue-router";
import { Prop, Vue } from "vue-property-decorator";
import { RouteLocationNormalizedLoaded } from "vue-router";
@Component
export default class SettingMenuItem extends Vue {
@Prop({ required: false, type: String }) title!: string;
@Prop({ required: true, type: Object }) to!: Route;
@Prop({ required: true, type: Object }) to!: RouteLocationNormalizedLoaded;
get isActive(): boolean {
if (!this.to) return false;

View File

@ -8,11 +8,11 @@
</li>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Options, Prop, Vue } from "vue-property-decorator";
import SettingMenuItem from "@/components/Settings/SettingMenuItem.vue";
import { Route } from "vue-router";
@Component({
@Options({
components: { SettingMenuItem },
})
export default class SettingMenuSection extends Vue {

Some files were not shown because too many files have changed in this diff Show More