Add global search
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
bfc936f57c
commit
48935e2168
@ -365,6 +365,14 @@ config :mobilizon, Mobilizon.Service.Pictures.Unsplash,
|
|||||||
app_name: "Mobilizon",
|
app_name: "Mobilizon",
|
||||||
access_key: nil
|
access_key: nil
|
||||||
|
|
||||||
|
config :mobilizon, :search, global: [is_default_search: false, is_enabled: true]
|
||||||
|
|
||||||
|
config :mobilizon, Mobilizon.Service.GlobalSearch,
|
||||||
|
service: Mobilizon.Service.GlobalSearch.SearchMobilizon
|
||||||
|
|
||||||
|
config :mobilizon, Mobilizon.Service.GlobalSearch.SearchMobilizon,
|
||||||
|
endpoint: "https://search.joinmobilizon.org"
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{config_env()}.exs"
|
import_config "#{config_env()}.exs"
|
||||||
|
@ -11,7 +11,7 @@ module.exports = {
|
|||||||
extends: [
|
extends: [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:vue/vue3-essential",
|
"plugin:vue/vue3-essential",
|
||||||
"@vue/eslint-config-typescript",
|
"@vue/eslint-config-typescript/recommended",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended",
|
||||||
"@vue/eslint-config-prettier",
|
"@vue/eslint-config-prettier",
|
||||||
],
|
],
|
||||||
@ -24,12 +24,11 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
"no-underscore-dangle": [
|
"no-underscore-dangle": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
allow: ["__typename"],
|
allow: ["__typename", "__schema"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
3
js/.gitignore
vendored
3
js/.gitignore
vendored
@ -24,3 +24,6 @@ yarn-error.log*
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const fetch = require("node-fetch");
|
import fetch from "node-fetch";
|
||||||
const fs = require("fs");
|
import fs from "fs";
|
||||||
|
|
||||||
fetch(`http://localhost:4000/api`, {
|
fetch(`http://localhost:4000/api`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
"@vue-leaflet/vue-leaflet": "^0.6.1",
|
"@vue-leaflet/vue-leaflet": "^0.6.1",
|
||||||
"@vue/apollo-composable": "^4.0.0-alpha.17",
|
"@vue/apollo-composable": "^4.0.0-alpha.17",
|
||||||
"@vue/compiler-sfc": "^3.2.37",
|
"@vue/compiler-sfc": "^3.2.37",
|
||||||
|
"@vueuse/core": "^9.1.0",
|
||||||
"@vueuse/head": "^0.7.9",
|
"@vueuse/head": "^0.7.9",
|
||||||
"@vueuse/router": "^9.0.2",
|
"@vueuse/router": "^9.0.2",
|
||||||
"@xiaoshuapp/draggable": "^4.1.0",
|
"@xiaoshuapp/draggable": "^4.1.0",
|
||||||
@ -93,6 +94,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@histoire/plugin-vue": "^0.10.0",
|
"@histoire/plugin-vue": "^0.10.0",
|
||||||
"@intlify/vite-plugin-vue-i18n": "^6.0.0",
|
"@intlify/vite-plugin-vue-i18n": "^6.0.0",
|
||||||
|
"@playwright/test": "^1.25.1",
|
||||||
"@rushstack/eslint-patch": "^1.1.4",
|
"@rushstack/eslint-patch": "^1.1.4",
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"@tailwindcss/typography": "^0.5.4",
|
"@tailwindcss/typography": "^0.5.4",
|
||||||
|
107
js/playwright.config.ts
Normal file
107
js/playwright.config.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import type { PlaywrightTestConfig } from "@playwright/test";
|
||||||
|
import { devices } from "@playwright/test";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
const config: PlaywrightTestConfig = {
|
||||||
|
testDir: "./tests/e2e",
|
||||||
|
/* Maximum time one test can run for. */
|
||||||
|
timeout: 30 * 1000,
|
||||||
|
expect: {
|
||||||
|
/**
|
||||||
|
* Maximum time expect() should wait for the condition to be met.
|
||||||
|
* For example in `await expect(locator).toHaveText();`
|
||||||
|
*/
|
||||||
|
timeout: 5000,
|
||||||
|
},
|
||||||
|
/* Run tests in files in parallel */
|
||||||
|
fullyParallel: true,
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
reporter: "html",
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||||
|
actionTimeout: 0,
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
baseURL: "http://localhost:4005",
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: "on-first-retry",
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Chrome"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "firefox",
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Firefox"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: 'webkit',
|
||||||
|
// use: {
|
||||||
|
// ...devices['Desktop Safari'],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: {
|
||||||
|
// ...devices['Pixel 5'],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: {
|
||||||
|
// ...devices['iPhone 12'],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: {
|
||||||
|
// channel: 'msedge',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: {
|
||||||
|
// channel: 'chrome',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||||
|
// outputDir: 'test-results/',
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
// webServer: {
|
||||||
|
// command: 'npm run start',
|
||||||
|
// port: 3000,
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
Before Width: | Height: | Size: 920 B After Width: | Height: | Size: 920 B |
@ -32,17 +32,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import NavBar from "./components/NavBar.vue";
|
import NavBar from "@/components/NavBar.vue";
|
||||||
import {
|
import {
|
||||||
AUTH_ACCESS_TOKEN,
|
AUTH_ACCESS_TOKEN,
|
||||||
AUTH_USER_EMAIL,
|
AUTH_USER_EMAIL,
|
||||||
AUTH_USER_ID,
|
AUTH_USER_ID,
|
||||||
AUTH_USER_ROLE,
|
AUTH_USER_ROLE,
|
||||||
} from "./constants";
|
} from "@/constants";
|
||||||
import { UPDATE_CURRENT_USER_CLIENT } from "./graphql/user";
|
import { UPDATE_CURRENT_USER_CLIENT } from "@/graphql/user";
|
||||||
import MobilizonFooter from "./components/Footer.vue";
|
import MobilizonFooter from "@/components/PageFooter.vue";
|
||||||
import jwt_decode, { JwtPayload } from "jwt-decode";
|
import jwt_decode, { JwtPayload } from "jwt-decode";
|
||||||
import { refreshAccessToken } from "./apollo/utils";
|
import { refreshAccessToken } from "@/apollo/utils";
|
||||||
import {
|
import {
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
@ -52,25 +52,30 @@ import {
|
|||||||
onBeforeMount,
|
onBeforeMount,
|
||||||
inject,
|
inject,
|
||||||
defineAsyncComponent,
|
defineAsyncComponent,
|
||||||
|
computed,
|
||||||
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { LocationType } from "./types/user-location.model";
|
import { LocationType } from "@/types/user-location.model";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||||
import { initializeCurrentActor } from "./utils/identity";
|
import { initializeCurrentActor } from "@/utils/identity";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { Snackbar } from "./plugins/snackbar";
|
import { Snackbar } from "@/plugins/snackbar";
|
||||||
import { Notifier } from "./plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
import {
|
import { CONFIG } from "@/graphql/config";
|
||||||
useIsDemoMode,
|
import { IConfig } from "@/types/config.model";
|
||||||
useServerProvidedLocation,
|
import { useRouter } from "vue-router";
|
||||||
} from "./composition/apollo/config";
|
|
||||||
|
const { result: configResult } = useQuery<{ config: IConfig }>(CONFIG);
|
||||||
|
|
||||||
|
const config = computed(() => configResult.value?.config);
|
||||||
|
|
||||||
const ErrorComponent = defineAsyncComponent(
|
const ErrorComponent = defineAsyncComponent(
|
||||||
() => import("./components/ErrorComponent.vue")
|
() => import("@/components/ErrorComponent.vue")
|
||||||
);
|
);
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
const { location } = useServerProvidedLocation();
|
const location = computed(() => config.value?.location);
|
||||||
|
|
||||||
const userLocation = reactive<LocationType>({
|
const userLocation = reactive<LocationType>({
|
||||||
lon: undefined,
|
lon: undefined,
|
||||||
@ -251,16 +256,19 @@ const showOfflineNetworkWarning = (): void => {
|
|||||||
// }, 0);
|
// }, 0);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// watch(config, async (configWatched: IConfig) => {
|
const router = useRouter();
|
||||||
// if (configWatched) {
|
|
||||||
// const { statistics } = (await import("./services/statistics")) as {
|
|
||||||
// statistics: (config: IConfig, environment: Record<string, any>) => void;
|
|
||||||
// };
|
|
||||||
// statistics(configWatched, { router, version: configWatched.version });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
const { isDemoMode } = useIsDemoMode();
|
watch(config, async (configWatched: IConfig | undefined) => {
|
||||||
|
if (configWatched) {
|
||||||
|
const { statistics } = await import("@/services/statistics");
|
||||||
|
statistics(configWatched?.analytics, {
|
||||||
|
router,
|
||||||
|
version: configWatched.version,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDemoMode = computed(() => config.value?.demoMode);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -83,7 +83,7 @@ const errorLink = onError(
|
|||||||
graphQLErrors.map(
|
graphQLErrors.map(
|
||||||
(graphQLError: GraphQLError & { status_code?: number }) => {
|
(graphQLError: GraphQLError & { status_code?: number }) => {
|
||||||
if (graphQLError?.status_code !== 401) {
|
if (graphQLError?.status_code !== 401) {
|
||||||
console.log(
|
console.debug(
|
||||||
`[GraphQL error]: Message: ${graphQLError.message}, Location: ${graphQLError.locations}, Path: ${graphQLError.path}`
|
`[GraphQL error]: Message: ${graphQLError.message}, Location: ${graphQLError.locations}, Path: ${graphQLError.path}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,22 +6,35 @@ import { authMiddleware } from "./auth";
|
|||||||
import errorLink from "./error-link";
|
import errorLink from "./error-link";
|
||||||
import { uploadLink } from "./absinthe-upload-socket-link";
|
import { uploadLink } from "./absinthe-upload-socket-link";
|
||||||
|
|
||||||
// const link = split(
|
let link;
|
||||||
// // split based on operation type
|
|
||||||
// ({ query }) => {
|
// The Absinthe socket Apollo link relies on an old library
|
||||||
// const definition = getMainDefinition(query);
|
// (@jumpn/utils-composite) which itself relies on an old
|
||||||
// return (
|
// Babel version, which is incompatible with Histoire.
|
||||||
// definition.kind === "OperationDefinition" &&
|
// We just don't use the absinthe apollo socket link
|
||||||
// definition.operation === "subscription"
|
// in this case.
|
||||||
// );
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// },
|
// @ts-ignore
|
||||||
// absintheSocketLink,
|
if (!import.meta.env.VITE_HISTOIRE_ENV) {
|
||||||
// uploadLink
|
// const absintheSocketLink = await import("./absinthe-socket-link");
|
||||||
// );
|
|
||||||
|
link = split(
|
||||||
|
// split based on operation type
|
||||||
|
({ query }) => {
|
||||||
|
const definition = getMainDefinition(query);
|
||||||
|
return (
|
||||||
|
definition.kind === "OperationDefinition" &&
|
||||||
|
definition.operation === "subscription"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
absintheSocketLink,
|
||||||
|
uploadLink
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const retryLink = new RetryLink();
|
const retryLink = new RetryLink();
|
||||||
|
|
||||||
export const fullLink = authMiddleware
|
export const fullLink = authMiddleware
|
||||||
.concat(retryLink)
|
.concat(retryLink)
|
||||||
.concat(errorLink)
|
.concat(errorLink)
|
||||||
.concat(uploadLink);
|
.concat(link ?? uploadLink);
|
||||||
|
@ -8,7 +8,7 @@ import { Resolvers } from "@apollo/client/core/types";
|
|||||||
export default function buildCurrentUserResolver(
|
export default function buildCurrentUserResolver(
|
||||||
cache: ApolloCache<NormalizedCacheObject>
|
cache: ApolloCache<NormalizedCacheObject>
|
||||||
): Resolvers {
|
): Resolvers {
|
||||||
cache.writeQuery({
|
cache?.writeQuery({
|
||||||
query: CURRENT_USER_CLIENT,
|
query: CURRENT_USER_CLIENT,
|
||||||
data: {
|
data: {
|
||||||
currentUser: {
|
currentUser: {
|
||||||
@ -21,7 +21,7 @@ export default function buildCurrentUserResolver(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
cache.writeQuery({
|
cache?.writeQuery({
|
||||||
query: CURRENT_ACTOR_CLIENT,
|
query: CURRENT_ACTOR_CLIENT,
|
||||||
data: {
|
data: {
|
||||||
currentActor: {
|
currentActor: {
|
||||||
@ -34,7 +34,7 @@ export default function buildCurrentUserResolver(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
cache.writeQuery({
|
cache?.writeQuery({
|
||||||
query: CURRENT_USER_LOCATION_CLIENT,
|
query: CURRENT_USER_LOCATION_CLIENT,
|
||||||
data: {
|
data: {
|
||||||
currentUserLocation: {
|
currentUserLocation: {
|
||||||
@ -70,8 +70,6 @@ export default function buildCurrentUserResolver(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
console.debug("updating current user", data);
|
|
||||||
|
|
||||||
localCache.writeQuery({ data, query: CURRENT_USER_CLIENT });
|
localCache.writeQuery({ data, query: CURRENT_USER_CLIENT });
|
||||||
},
|
},
|
||||||
updateCurrentActor: (
|
updateCurrentActor: (
|
||||||
|
@ -73,6 +73,9 @@ export const typePolicies: TypePolicies = {
|
|||||||
Config: {
|
Config: {
|
||||||
merge: true,
|
merge: true,
|
||||||
},
|
},
|
||||||
|
Address: {
|
||||||
|
keyFields: ["id"],
|
||||||
|
},
|
||||||
RootQueryType: {
|
RootQueryType: {
|
||||||
fields: {
|
fields: {
|
||||||
relayFollowers: paginatedLimitPagination<IFollower>(),
|
relayFollowers: paginatedLimitPagination<IFollower>(),
|
||||||
@ -110,7 +113,7 @@ export async function refreshAccessToken(): Promise<boolean> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Refreshing access token.");
|
console.debug("Refreshing access token.");
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const { mutate, onDone, onError } = provideApolloClient(apolloClient)(() =>
|
const { mutate, onDone, onError } = provideApolloClient(apolloClient)(() =>
|
||||||
@ -130,7 +133,7 @@ export async function refreshAccessToken(): Promise<boolean> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onError((err) => {
|
onError((err) => {
|
||||||
console.debug("Failed to refresh token");
|
console.debug("Failed to refresh token", err);
|
||||||
reject(false);
|
reject(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
body {
|
body {
|
||||||
@apply bg-body-background-color dark:bg-gray-700 dark:text-white;
|
@apply bg-body-background-color dark:bg-zinc-800 dark:text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Button */
|
/* Button */
|
||||||
.btn {
|
.btn {
|
||||||
outline: none !important;
|
@apply font-bold py-2 px-4 bg-mbz-bluegreen hover:bg-mbz-bluegreen-600 text-white rounded h-10 outline-none focus:ring ring-offset-1 ring-offset-slate-50 ring-blue-300;
|
||||||
@apply font-bold py-2 px-4 bg-mbz-bluegreen dark:bg-violet-3 text-white rounded h-10;
|
|
||||||
}
|
}
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
@apply text-slate-200;
|
@apply text-slate-200;
|
||||||
@ -28,11 +27,14 @@ body {
|
|||||||
@apply opacity-50 cursor-not-allowed;
|
@apply opacity-50 cursor-not-allowed;
|
||||||
}
|
}
|
||||||
.btn-danger {
|
.btn-danger {
|
||||||
@apply bg-mbz-danger;
|
@apply bg-mbz-danger hover:bg-mbz-danger/90;
|
||||||
}
|
}
|
||||||
.btn-success {
|
.btn-success {
|
||||||
@apply bg-mbz-success;
|
@apply bg-mbz-success;
|
||||||
}
|
}
|
||||||
|
.btn-text {
|
||||||
|
@apply bg-transparent border-transparent text-black dark:text-white font-normal underline hover:bg-zinc-200 hover:text-black;
|
||||||
|
}
|
||||||
|
|
||||||
/* Field */
|
/* Field */
|
||||||
.field {
|
.field {
|
||||||
@ -62,7 +64,7 @@ body {
|
|||||||
|
|
||||||
/* Input */
|
/* Input */
|
||||||
.input {
|
.input {
|
||||||
@apply appearance-none border w-full py-2 px-3 text-black leading-tight;
|
@apply appearance-none border w-full py-2 px-3 text-black leading-tight dark:bg-zinc-600 dark:placeholder:text-zinc-400 dark:text-zinc-50;
|
||||||
}
|
}
|
||||||
.input-danger {
|
.input-danger {
|
||||||
@apply border-red-500;
|
@apply border-red-500;
|
||||||
@ -70,6 +72,10 @@ body {
|
|||||||
.input-icon-right {
|
.input-icon-right {
|
||||||
right: 0.5rem;
|
right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
.input[type="text"]:disabled,
|
||||||
|
.input[type="email"]:disabled {
|
||||||
|
@apply bg-zinc-200 dark:bg-zinc-400;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-warning {
|
.icon-warning {
|
||||||
@apply text-amber-600;
|
@apply text-amber-600;
|
||||||
@ -78,6 +84,12 @@ body {
|
|||||||
.icon-danger {
|
.icon-danger {
|
||||||
@apply text-red-500;
|
@apply text-red-500;
|
||||||
}
|
}
|
||||||
|
.icon-success {
|
||||||
|
@apply text-mbz-success;
|
||||||
|
}
|
||||||
|
.icon-grey {
|
||||||
|
@apply text-gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
.o-input__icon-left {
|
.o-input__icon-left {
|
||||||
@apply dark:text-black h-10 w-10;
|
@apply dark:text-black h-10 w-10;
|
||||||
@ -111,25 +123,27 @@ body {
|
|||||||
}
|
}
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
min-width: 12em;
|
min-width: 12em;
|
||||||
@apply bg-white dark:bg-gray-700 shadow-lg rounded text-start py-2;
|
@apply bg-white dark:bg-zinc-700 shadow-lg rounded text-start py-2;
|
||||||
}
|
}
|
||||||
.dropdown-item {
|
.dropdown-item {
|
||||||
@apply relative inline-flex gap-1 no-underline p-2 cursor-pointer w-full;
|
@apply relative inline-flex gap-1 no-underline p-2 cursor-pointer w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item-active {
|
.dropdown-item-active {
|
||||||
/* @apply bg-violet-2; */
|
@apply bg-white text-black;
|
||||||
@apply bg-white;
|
}
|
||||||
|
.dropdown-button {
|
||||||
|
@apply inline-flex gap-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkbox */
|
/* Checkbox */
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
@apply appearance-none bg-blue-500 border-blue-500;
|
@apply appearance-none bg-primary border-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-checked {
|
.checkbox-checked {
|
||||||
@apply bg-blue-500;
|
@apply bg-primary text-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-label {
|
.checkbox-label {
|
||||||
@ -139,7 +153,7 @@ body {
|
|||||||
/* Modal */
|
/* Modal */
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
@apply bg-white dark:bg-gray-700 rounded px-2 py-4 w-full;
|
@apply bg-white dark:bg-zinc-800 rounded px-2 py-4 w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Switch */
|
/* Switch */
|
||||||
@ -151,14 +165,18 @@ body {
|
|||||||
@apply pl-2;
|
@apply pl-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.switch-check-checked {
|
||||||
|
@apply bg-primary;
|
||||||
|
}
|
||||||
|
|
||||||
/* Select */
|
/* Select */
|
||||||
.select {
|
.select {
|
||||||
@apply dark:bg-white dark:text-black rounded pl-2 pr-6 border-2 border-transparent h-10 shadow-none;
|
@apply dark:bg-zinc-600 dark:placeholder:text-zinc-400 dark:text-zinc-50 rounded pl-2 pr-6 border-2 border-transparent h-10 shadow-none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Radio */
|
/* Radio */
|
||||||
.form-radio {
|
.form-radio {
|
||||||
@apply bg-none;
|
@apply bg-none text-primary accent-primary;
|
||||||
}
|
}
|
||||||
.radio-label {
|
.radio-label {
|
||||||
@apply pl-2;
|
@apply pl-2;
|
||||||
@ -171,7 +189,7 @@ button.menubar__button {
|
|||||||
|
|
||||||
/* Notification */
|
/* Notification */
|
||||||
.notification {
|
.notification {
|
||||||
@apply p-7 bg-secondary text-black rounded;
|
@apply p-7 bg-mbz-yellow-alt-200 dark:bg-mbz-purple-600 text-black dark:text-white rounded;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-primary {
|
.notification-primary {
|
||||||
@ -187,18 +205,26 @@ button.menubar__button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notification-danger {
|
.notification-danger {
|
||||||
@apply bg-mbz-danger;
|
@apply bg-mbz-danger text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table */
|
/* Table */
|
||||||
.table tr {
|
.table tr {
|
||||||
@apply odd:bg-white dark:odd:bg-gray-800 even:bg-gray-50 dark:even:bg-gray-900 border-b;
|
@apply odd:bg-white dark:odd:bg-zinc-600 even:bg-gray-50 dark:even:bg-zinc-700 border-b rounded;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-td {
|
.table-td {
|
||||||
@apply py-4 px-2 whitespace-nowrap;
|
@apply py-4 px-2 whitespace-nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-th {
|
||||||
|
@apply p-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-root {
|
||||||
|
@apply mt-4;
|
||||||
|
}
|
||||||
|
|
||||||
/* Snackbar */
|
/* Snackbar */
|
||||||
.notification-dark {
|
.notification-dark {
|
||||||
@apply text-white;
|
@apply text-white;
|
||||||
@ -210,14 +236,14 @@ button.menubar__button {
|
|||||||
@apply flex items-center text-center justify-between;
|
@apply flex items-center text-center justify-between;
|
||||||
}
|
}
|
||||||
.pagination-link {
|
.pagination-link {
|
||||||
@apply inline-flex items-center relative justify-center cursor-pointer rounded h-10 m-1 p-2 bg-white text-lg;
|
@apply inline-flex items-center relative justify-center cursor-pointer rounded h-10 m-1 p-2 bg-white dark:bg-zinc-300 text-lg text-black;
|
||||||
}
|
}
|
||||||
.pagination-list {
|
.pagination-list {
|
||||||
@apply flex items-center text-center list-none flex-wrap grow shrink justify-start;
|
@apply flex items-center text-center list-none flex-wrap grow shrink justify-start;
|
||||||
}
|
}
|
||||||
.pagination-next,
|
.pagination-next,
|
||||||
.pagination-previous {
|
.pagination-previous {
|
||||||
@apply px-3;
|
@apply px-3 dark:text-black;
|
||||||
}
|
}
|
||||||
.pagination-link-current {
|
.pagination-link-current {
|
||||||
@apply bg-primary cursor-not-allowed pointer-events-none border-primary text-white;
|
@apply bg-primary cursor-not-allowed pointer-events-none border-primary text-white;
|
||||||
@ -236,3 +262,19 @@ button.menubar__button {
|
|||||||
.tabs-nav-item-active-boxed {
|
.tabs-nav-item-active-boxed {
|
||||||
@apply bg-white border-gray-300 text-primary;
|
@apply bg-white border-gray-300 text-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Tooltip */
|
||||||
|
.tooltip-content {
|
||||||
|
@apply bg-zinc-800 text-white dark:bg-zinc-300 dark:text-black rounded py-1 px-2;
|
||||||
|
}
|
||||||
|
.tooltip-arrow {
|
||||||
|
@apply text-zinc-800 dark:text-zinc-200;
|
||||||
|
}
|
||||||
|
.tooltip-content-success {
|
||||||
|
@apply bg-mbz-success text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tiptap editor */
|
||||||
|
.menubar__button {
|
||||||
|
@apply hover:bg-[rgba(0,0,0,.05)];
|
||||||
|
}
|
||||||
|
@ -22,13 +22,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
.mbz-card {
|
.mbz-card {
|
||||||
@apply block bg-mbz-yellow hover:bg-mbz-yellow/90 text-violet-title dark:text-white dark:hover:text-white/90 rounded-lg dark:border-violet-title shadow-md dark:bg-gray-700 dark:hover:bg-gray-700/90 dark:text-white dark:hover:text-white;
|
@apply block bg-mbz-yellow-alt-300 hover:bg-mbz-yellow-alt-200 text-violet-title dark:text-white dark:hover:text-white rounded-lg dark:border-violet-title shadow-md dark:bg-mbz-purple dark:hover:dark:bg-mbz-purple-400 dark:text-white dark:hover:text-white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
>
|
>
|
||||||
{{ displayName(actor) }}
|
{{ displayName(actor) }}
|
||||||
</h5>
|
</h5>
|
||||||
<p class="text-gray-500 truncate" v-if="actor.name">
|
<p class="text-gray-500 dark:text-gray-200 truncate" v-if="actor.name">
|
||||||
<span dir="ltr">@{{ usernameWithDomain(actor) }}</span>
|
<span dir="ltr">@{{ usernameWithDomain(actor) }}</span>
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="activity-item">
|
<div class="activity-item">
|
||||||
<o-icon :icon="'chat'" :type="iconColor" />
|
<o-icon :icon="'chat'" :variant="iconColor" custom-size="24" />
|
||||||
<div class="subject">
|
<div class="mt-1 ml-2 prose dark:prose-invert prose-p:m-0">
|
||||||
<i18n-t :keypath="translation" tag="p">
|
<i18n-t :keypath="translation" tag="p">
|
||||||
<template #discussion>
|
<template #discussion>
|
||||||
<router-link
|
<router-link
|
||||||
@ -102,12 +102,12 @@ const iconColor = computed((): string | undefined => {
|
|||||||
switch (props.activity.subject) {
|
switch (props.activity.subject) {
|
||||||
case ActivityDiscussionSubject.DISCUSSION_CREATED:
|
case ActivityDiscussionSubject.DISCUSSION_CREATED:
|
||||||
case ActivityDiscussionSubject.DISCUSSION_REPLIED:
|
case ActivityDiscussionSubject.DISCUSSION_REPLIED:
|
||||||
return "is-success";
|
return "success";
|
||||||
case ActivityDiscussionSubject.DISCUSSION_RENAMED:
|
case ActivityDiscussionSubject.DISCUSSION_RENAMED:
|
||||||
case ActivityDiscussionSubject.DISCUSSION_ARCHIVED:
|
case ActivityDiscussionSubject.DISCUSSION_ARCHIVED:
|
||||||
return "is-grey";
|
return "grey";
|
||||||
case ActivityDiscussionSubject.DISCUSSION_DELETED:
|
case ActivityDiscussionSubject.DISCUSSION_DELETED:
|
||||||
return "is-danger";
|
return "danger";
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="activity-item">
|
<div class="activity-item">
|
||||||
<o-icon :icon="'calendar'" :type="iconColor" />
|
<o-icon :icon="'calendar'" :variant="iconColor" custom-size="24" />
|
||||||
<div class="subject">
|
<div class="mt-1 ml-2 prose dark:prose-invert prose-p:m-0">
|
||||||
<i18n-t :keypath="translation" tag="p">
|
<i18n-t :keypath="translation" tag="p">
|
||||||
<template #event>
|
<template #event>
|
||||||
<router-link
|
<router-link
|
||||||
@ -93,11 +93,11 @@ const iconColor = computed((): string | undefined => {
|
|||||||
switch (props.activity.subject) {
|
switch (props.activity.subject) {
|
||||||
case ActivityEventSubject.EVENT_CREATED:
|
case ActivityEventSubject.EVENT_CREATED:
|
||||||
case ActivityEventCommentSubject.COMMENT_POSTED:
|
case ActivityEventCommentSubject.COMMENT_POSTED:
|
||||||
return "is-success";
|
return "success";
|
||||||
case ActivityEventSubject.EVENT_UPDATED:
|
case ActivityEventSubject.EVENT_UPDATED:
|
||||||
return "is-grey";
|
return "grey";
|
||||||
case ActivityEventSubject.EVENT_DELETED:
|
case ActivityEventSubject.EVENT_DELETED:
|
||||||
return "is-danger";
|
return "danger";
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="activity-item">
|
<div class="activity-item">
|
||||||
<o-icon :icon="'cog'" :type="iconColor" />
|
<o-icon :icon="'cog'" :variant="iconColor" custom-size="24" />
|
||||||
<div class="subject">
|
<div class="mt-1 ml-2 prose dark:prose-invert prose-p:m-0">
|
||||||
<i18n-t :keypath="translation" tag="p">
|
<i18n-t :keypath="translation" tag="p">
|
||||||
<template #group>
|
<template #group>
|
||||||
<router-link
|
<router-link
|
||||||
@ -28,13 +28,7 @@
|
|||||||
></template
|
></template
|
||||||
></i18n-t
|
></i18n-t
|
||||||
>
|
>
|
||||||
<i18n-t
|
<i18n-t :keypath="detail" v-for="detail in details" :key="detail" tag="p">
|
||||||
:keypath="detail"
|
|
||||||
v-for="detail in details"
|
|
||||||
:key="detail"
|
|
||||||
tag="p"
|
|
||||||
class="has-text-grey-dark"
|
|
||||||
>
|
|
||||||
<template #profile>
|
<template #profile>
|
||||||
<popover-actor-card :actor="activity.author" :inline="true">
|
<popover-actor-card :actor="activity.author" :inline="true">
|
||||||
<b>
|
<b>
|
||||||
@ -63,9 +57,7 @@
|
|||||||
}}</b>
|
}}</b>
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
<small class="has-text-grey-dark activity-date">{{
|
<small>{{ formatTimeString(activity.insertedAt) }}</small>
|
||||||
formatTimeString(activity.insertedAt)
|
|
||||||
}}</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -110,9 +102,9 @@ const translation = computed((): string | undefined => {
|
|||||||
const iconColor = computed((): string | undefined => {
|
const iconColor = computed((): string | undefined => {
|
||||||
switch (props.activity.subject) {
|
switch (props.activity.subject) {
|
||||||
case ActivityGroupSubject.GROUP_CREATED:
|
case ActivityGroupSubject.GROUP_CREATED:
|
||||||
return "is-success";
|
return "success";
|
||||||
case ActivityGroupSubject.GROUP_UPDATED:
|
case ActivityGroupSubject.GROUP_UPDATED:
|
||||||
return "is-grey";
|
return "grey";
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="activity-item">
|
<div class="activity-item">
|
||||||
<o-icon :icon="icon" :type="iconColor" />
|
<o-icon :icon="icon" :variant="iconColor" custom-size="24" />
|
||||||
<div class="subject">
|
<div class="mt-1 ml-2 prose dark:prose-invert prose-p:m-0">
|
||||||
<i18n-t :keypath="translation" tag="p">
|
<i18n-t :keypath="translation" tag="p">
|
||||||
<template #member>
|
<template #member>
|
||||||
<popover-actor-card
|
<popover-actor-card
|
||||||
@ -144,14 +144,14 @@ const iconColor = computed((): string | undefined => {
|
|||||||
case ActivityMemberSubject.MEMBER_JOINED:
|
case ActivityMemberSubject.MEMBER_JOINED:
|
||||||
case ActivityMemberSubject.MEMBER_APPROVED:
|
case ActivityMemberSubject.MEMBER_APPROVED:
|
||||||
case ActivityMemberSubject.MEMBER_ACCEPTED_INVITATION:
|
case ActivityMemberSubject.MEMBER_ACCEPTED_INVITATION:
|
||||||
return "is-success";
|
return "success";
|
||||||
case ActivityMemberSubject.MEMBER_REQUEST:
|
case ActivityMemberSubject.MEMBER_REQUEST:
|
||||||
case ActivityMemberSubject.MEMBER_UPDATED:
|
case ActivityMemberSubject.MEMBER_UPDATED:
|
||||||
return "is-grey";
|
return "grey";
|
||||||
case ActivityMemberSubject.MEMBER_REMOVED:
|
case ActivityMemberSubject.MEMBER_REMOVED:
|
||||||
case ActivityMemberSubject.MEMBER_REJECTED_INVITATION:
|
case ActivityMemberSubject.MEMBER_REJECTED_INVITATION:
|
||||||
case ActivityMemberSubject.MEMBER_QUIT:
|
case ActivityMemberSubject.MEMBER_QUIT:
|
||||||
return "is-danger";
|
return "danger";
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="activity-item">
|
<div class="activity-item">
|
||||||
<o-icon :icon="'bullhorn'" :type="iconColor" />
|
<o-icon :icon="'bullhorn'" :variant="iconColor" custom-size="24" />
|
||||||
<div class="subject">
|
<div class="mt-1 ml-2 prose dark:prose-invert prose-p:m-0">
|
||||||
<i18n-t :keypath="translation" tag="p">
|
<i18n-t :keypath="translation" tag="p">
|
||||||
<template #post>
|
<template #post>
|
||||||
<router-link
|
<router-link
|
||||||
@ -78,11 +78,11 @@ const translation = computed((): string | undefined => {
|
|||||||
const iconColor = computed((): string | undefined => {
|
const iconColor = computed((): string | undefined => {
|
||||||
switch (props.activity.subject) {
|
switch (props.activity.subject) {
|
||||||
case ActivityPostSubject.POST_CREATED:
|
case ActivityPostSubject.POST_CREATED:
|
||||||
return "is-success";
|
return "success";
|
||||||
case ActivityPostSubject.POST_UPDATED:
|
case ActivityPostSubject.POST_UPDATED:
|
||||||
return "is-grey";
|
return "grey";
|
||||||
case ActivityPostSubject.POST_DELETED:
|
case ActivityPostSubject.POST_DELETED:
|
||||||
return "is-danger";
|
return "danger";
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="activity-item">
|
<div class="activity-item">
|
||||||
<o-icon :icon="'link'" :type="iconColor" />
|
<o-icon :icon="'link'" :variant="iconColor" custom-size="24" />
|
||||||
<div class="subject">
|
<div class="mt-1 ml-2 prose dark:prose-invert prose-p:m-0">
|
||||||
<i18n-t :keypath="translation" tag="p">
|
<i18n-t :keypath="translation" tag="p">
|
||||||
<template #resource>
|
<template #resource>
|
||||||
<router-link v-if="activity.object" :to="path">{{
|
<router-link v-if="activity.object" :to="path">{{
|
||||||
@ -142,12 +142,12 @@ const translation = computed((): string | undefined => {
|
|||||||
const iconColor = computed((): string | undefined => {
|
const iconColor = computed((): string | undefined => {
|
||||||
switch (props.activity.subject) {
|
switch (props.activity.subject) {
|
||||||
case ActivityResourceSubject.RESOURCE_CREATED:
|
case ActivityResourceSubject.RESOURCE_CREATED:
|
||||||
return "is-success";
|
return "success";
|
||||||
case ActivityResourceSubject.RESOURCE_MOVED:
|
case ActivityResourceSubject.RESOURCE_MOVED:
|
||||||
case ActivityResourceSubject.RESOURCE_UPDATED:
|
case ActivityResourceSubject.RESOURCE_UPDATED:
|
||||||
return "is-grey";
|
return "grey";
|
||||||
case ActivityResourceSubject.RESOURCE_DELETED:
|
case ActivityResourceSubject.RESOURCE_DELETED:
|
||||||
return "is-danger";
|
return "danger";
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.activity-item {
|
.activity-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
span.icon {
|
span.o-icon {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -10,8 +10,4 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.subject {
|
|
||||||
padding: 0.25rem 0 0 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<o-icon
|
<o-icon
|
||||||
v-if="showIcon"
|
v-if="showIcon"
|
||||||
:icon="poiInfos?.poiIcon.icon"
|
:icon="poiInfos?.poiIcon.icon"
|
||||||
size="is-medium"
|
size="medium"
|
||||||
class="icon"
|
class="icon"
|
||||||
/>
|
/>
|
||||||
<p>
|
<p>
|
||||||
|
@ -1,104 +1,3 @@
|
|||||||
export const eventCategories = (t) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: "ARTS",
|
|
||||||
icon: "palette",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "BOOK_CLUBS",
|
|
||||||
icon: "favourite-book",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "BUSINESS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "CAUSES",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "COMEDY",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "CRAFTS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "FOOD_DRINK",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "HEALTH",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "MUSIC",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "AUTO_BOAT_AIR",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "COMMUNITY",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "FAMILY_EDUCATION",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "FASHION_BEAUTY",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "FILM_MEDIA",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "GAMES",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "LANGUAGE_CULTURE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "LEARNING",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "LGBTQ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "MOVEMENTS_POLITICS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "NETWORKING",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "PARTY",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "PERFORMING_VISUAL_ARTS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "PETS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "PHOTOGRAPHY",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "OUTDOORS_ADVENTURE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "SPIRITUALITY_RELIGION_BELIEFS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "SCIENCE_TECH",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "SPORTS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "THEATRE",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "MEETING",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const eventCategoryLabel = (category: string, t): string | undefined => {
|
|
||||||
return eventCategories(t).find(({ id }) => id === category)?.label;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CategoryPictureLicencingElement = { name: string; url: string };
|
export type CategoryPictureLicencingElement = { name: string; url: string };
|
||||||
export type CategoryPictureLicencing = {
|
export type CategoryPictureLicencing = {
|
||||||
author: CategoryPictureLicencingElement;
|
author: CategoryPictureLicencingElement;
|
||||||
|
@ -85,7 +85,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Comment from "@/components/Comment/Comment.vue";
|
import Comment from "@/components/Comment/EventComment.vue";
|
||||||
import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue";
|
import IdentityPickerWrapper from "@/views/Account/IdentityPickerWrapper.vue";
|
||||||
import { CommentModeration } from "@/types/enums";
|
import { CommentModeration } from "@/types/enums";
|
||||||
import { CommentModel, IComment } from "../../types/comment.model";
|
import { CommentModel, IComment } from "../../types/comment.model";
|
||||||
@ -122,7 +122,9 @@ const props = defineProps<{
|
|||||||
newComment?: IComment;
|
newComment?: IComment;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const Editor = defineAsyncComponent(() => import("@/components/Editor.vue"));
|
const Editor = defineAsyncComponent(
|
||||||
|
() => import("@/components/TextEditor.vue")
|
||||||
|
);
|
||||||
|
|
||||||
const newComment = ref<IComment>(props.newComment ?? new CommentModel());
|
const newComment = ref<IComment>(props.newComment ?? new CommentModel());
|
||||||
|
|
||||||
@ -284,7 +286,7 @@ const { mutate: deleteComment, onError: deleteCommentMutationError } =
|
|||||||
replies: updatedReplies,
|
replies: updatedReplies,
|
||||||
totalReplies: parentComment.totalReplies - 1,
|
totalReplies: parentComment.totalReplies - 1,
|
||||||
});
|
});
|
||||||
console.log("updatedComments", updatedComments);
|
console.debug("updatedComments", updatedComments);
|
||||||
} else {
|
} else {
|
||||||
// we have deleted a thread itself
|
// we have deleted a thread itself
|
||||||
updatedComments = updatedComments.map((reply) => {
|
updatedComments = updatedComments.map((reply) => {
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</Story>
|
</Story>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { IActor } from "@/types/actor";
|
import { IPerson } from "@/types/actor";
|
||||||
import { IComment } from "@/types/comment.model";
|
import { IComment } from "@/types/comment.model";
|
||||||
import {
|
import {
|
||||||
ActorType,
|
ActorType,
|
||||||
@ -34,7 +34,7 @@ import {
|
|||||||
} from "@/types/enums";
|
} from "@/types/enums";
|
||||||
import { IEvent } from "@/types/event.model";
|
import { IEvent } from "@/types/event.model";
|
||||||
import { reactive } from "vue";
|
import { reactive } from "vue";
|
||||||
import Comment from "./Comment.vue";
|
import Comment from "./EventComment.vue";
|
||||||
import FloatingVue from "floating-vue";
|
import FloatingVue from "floating-vue";
|
||||||
import "floating-vue/dist/style.css";
|
import "floating-vue/dist/style.css";
|
||||||
import { hstEvent } from "histoire/client";
|
import { hstEvent } from "histoire/client";
|
||||||
@ -51,7 +51,7 @@ const baseActorAvatar = {
|
|||||||
url: "https://social.tcit.fr/system/accounts/avatars/000/000/001/original/a28c50ce5f2b13fd.jpg",
|
url: "https://social.tcit.fr/system/accounts/avatars/000/000/001/original/a28c50ce5f2b13fd.jpg",
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseActor: IActor = {
|
const baseActor: IPerson = {
|
||||||
name: "Thomas Citharel",
|
name: "Thomas Citharel",
|
||||||
preferredUsername: "tcit",
|
preferredUsername: "tcit",
|
||||||
avatar: baseActorAvatar,
|
avatar: baseActorAvatar,
|
||||||
@ -67,8 +67,8 @@ const baseEvent: IEvent = {
|
|||||||
uuid: "",
|
uuid: "",
|
||||||
title: "A very interesting event",
|
title: "A very interesting event",
|
||||||
description: "Things happen",
|
description: "Things happen",
|
||||||
beginsOn: new Date(),
|
beginsOn: new Date().toISOString(),
|
||||||
endsOn: new Date(),
|
endsOn: new Date().toISOString(),
|
||||||
physicalAddress: {
|
physicalAddress: {
|
||||||
description: "Somewhere",
|
description: "Somewhere",
|
||||||
street: "",
|
street: "",
|
||||||
@ -88,7 +88,7 @@ const baseEvent: IEvent = {
|
|||||||
url: "",
|
url: "",
|
||||||
local: true,
|
local: true,
|
||||||
slug: "",
|
slug: "",
|
||||||
publishAt: new Date(),
|
publishAt: new Date().toISOString(),
|
||||||
status: EventStatus.CONFIRMED,
|
status: EventStatus.CONFIRMED,
|
||||||
visibility: EventVisibility.PUBLIC,
|
visibility: EventVisibility.PUBLIC,
|
||||||
joinOptions: EventJoinOptions.FREE,
|
joinOptions: EventJoinOptions.FREE,
|
||||||
@ -151,7 +151,7 @@ const comment = reactive<IComment>({
|
|||||||
text: "a reply!",
|
text: "a reply!",
|
||||||
id: "90",
|
id: "90",
|
||||||
actor: baseActor,
|
actor: baseActor,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date().toISOString(),
|
||||||
url: "http://somewhere.tld",
|
url: "http://somewhere.tld",
|
||||||
replies: [],
|
replies: [],
|
||||||
totalReplies: 0,
|
totalReplies: 0,
|
||||||
@ -162,7 +162,7 @@ const comment = reactive<IComment>({
|
|||||||
text: "a reply to another reply!",
|
text: "a reply to another reply!",
|
||||||
id: "92",
|
id: "92",
|
||||||
actor: baseActor,
|
actor: baseActor,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date().toISOString(),
|
||||||
url: "http://somewhere.tld",
|
url: "http://somewhere.tld",
|
||||||
replies: [],
|
replies: [],
|
||||||
totalReplies: 0,
|
totalReplies: 0,
|
||||||
@ -171,7 +171,7 @@ const comment = reactive<IComment>({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
isAnnouncement: false,
|
isAnnouncement: false,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date().toISOString(),
|
||||||
url: "http://somewhere.tld",
|
url: "http://somewhere.tld",
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
@ -175,7 +175,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import EditorComponent from "@/components/Editor.vue";
|
import EditorComponent from "@/components/TextEditor.vue";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import { CommentModeration } from "@/types/enums";
|
import { CommentModeration } from "@/types/enums";
|
||||||
import { CommentModel, IComment } from "../../types/comment.model";
|
import { CommentModel, IComment } from "../../types/comment.model";
|
||||||
@ -200,7 +200,9 @@ import ChevronDown from "vue-material-design-icons/ChevronDown.vue";
|
|||||||
import Reply from "vue-material-design-icons/Reply.vue";
|
import Reply from "vue-material-design-icons/Reply.vue";
|
||||||
import type { Locale } from "date-fns";
|
import type { Locale } from "date-fns";
|
||||||
|
|
||||||
const Editor = defineAsyncComponent(() => import("@/components/Editor.vue"));
|
const Editor = defineAsyncComponent(
|
||||||
|
() => import("@/components/TextEditor.vue")
|
||||||
|
);
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@ -257,7 +259,7 @@ const replyToComment = (): void => {
|
|||||||
newComment.value.inReplyToComment = props.comment;
|
newComment.value.inReplyToComment = props.comment;
|
||||||
newComment.value.originComment = props.comment.originComment ?? props.comment;
|
newComment.value.originComment = props.comment.originComment ?? props.comment;
|
||||||
newComment.value.actor = props.currentActor;
|
newComment.value.actor = props.currentActor;
|
||||||
console.log(newComment.value);
|
console.debug(newComment.value);
|
||||||
emit("create-comment", newComment.value);
|
emit("create-comment", newComment.value);
|
||||||
newComment.value = new CommentModel();
|
newComment.value = new CommentModel();
|
||||||
replyTo.value = false;
|
replyTo.value = false;
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<article class="flex gap-2">
|
<article class="flex gap-2 bg-white dark:bg-transparent">
|
||||||
<div class="">
|
<div class="">
|
||||||
<figure class="" v-if="comment.actor && comment.actor.avatar">
|
<figure class="" v-if="comment.actor && comment.actor.avatar">
|
||||||
<img
|
<img
|
||||||
@ -32,7 +32,7 @@
|
|||||||
comment.actor.id === currentActor?.id
|
comment.actor.id === currentActor?.id
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<o-dropdown aria-role="list">
|
<o-dropdown aria-role="list" position="bottom-left">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<o-icon role="button" icon="dots-horizontal" />
|
<o-icon role="button" icon="dots-horizontal" />
|
||||||
</template>
|
</template>
|
||||||
@ -133,7 +133,9 @@ import { formatDateTimeString } from "@/filters/datetime";
|
|||||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||||
import type { Locale } from "date-fns";
|
import type { Locale } from "date-fns";
|
||||||
|
|
||||||
const Editor = defineAsyncComponent(() => import("@/components/Editor.vue"));
|
const Editor = defineAsyncComponent(
|
||||||
|
() => import("@/components/TextEditor.vue")
|
||||||
|
);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: IComment;
|
modelValue: IComment;
|
||||||
|
@ -68,7 +68,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary class="is-size-5">{{ t("Technical details") }}</summary>
|
<summary>{{ t("Technical details") }}</summary>
|
||||||
<p>{{ t("Error message") }}</p>
|
<p>{{ t("Error message") }}</p>
|
||||||
<pre>{{ error }}</pre>
|
<pre>{{ error }}</pre>
|
||||||
<p>{{ t("Error stacktrace") }}</p>
|
<p>{{ t("Error stacktrace") }}</p>
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
<Variant title="cancelled">
|
<Variant title="cancelled">
|
||||||
<EventCard :event="cancelledEvent" />
|
<EventCard :event="cancelledEvent" />
|
||||||
</Variant>
|
</Variant>
|
||||||
|
<Variant title="Row mode">
|
||||||
|
<EventCard :event="longEvent" mode="row" />
|
||||||
|
</Variant>
|
||||||
</Story>
|
</Story>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -53,8 +56,8 @@ const baseEvent: IEvent = {
|
|||||||
uuid: "",
|
uuid: "",
|
||||||
title: "A very interesting event",
|
title: "A very interesting event",
|
||||||
description: "Things happen",
|
description: "Things happen",
|
||||||
beginsOn: new Date(),
|
beginsOn: new Date().toISOString(),
|
||||||
endsOn: new Date(),
|
endsOn: new Date().toISOString(),
|
||||||
physicalAddress: {
|
physicalAddress: {
|
||||||
description: "Somewhere",
|
description: "Somewhere",
|
||||||
street: "",
|
street: "",
|
||||||
@ -74,7 +77,7 @@ const baseEvent: IEvent = {
|
|||||||
url: "",
|
url: "",
|
||||||
local: true,
|
local: true,
|
||||||
slug: "",
|
slug: "",
|
||||||
publishAt: new Date(),
|
publishAt: new Date().toISOString(),
|
||||||
status: EventStatus.CONFIRMED,
|
status: EventStatus.CONFIRMED,
|
||||||
visibility: EventVisibility.PUBLIC,
|
visibility: EventVisibility.PUBLIC,
|
||||||
joinOptions: EventJoinOptions.FREE,
|
joinOptions: EventJoinOptions.FREE,
|
||||||
@ -130,7 +133,7 @@ const event = reactive<IEvent>(baseEvent);
|
|||||||
const longEvent = reactive<IEvent>({
|
const longEvent = reactive<IEvent>({
|
||||||
...baseEvent,
|
...baseEvent,
|
||||||
title:
|
title:
|
||||||
"A very long title that will have trouble to display because it will take multiple lines but where will it stop ?! Maybe after 3 lines is enough. Let's say so.",
|
"A very long title that will have trouble to display because it will take multiple lines but where will it stop ?! Maybe after 3 lines is enough. Let's say so. But if it doesn't work, we really need to truncate it at some point. Definitively.",
|
||||||
});
|
});
|
||||||
|
|
||||||
const tentativeEvent = reactive<IEvent>({
|
const tentativeEvent = reactive<IEvent>({
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-link
|
<LinkOrRouterLink
|
||||||
class="mbz-card max-w-xs shrink-0 w-[18rem] snap-center dark:bg-mbz-purple"
|
class="mbz-card snap-center dark:bg-mbz-purple"
|
||||||
:to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"
|
:class="{
|
||||||
|
'sm:flex sm:items-start': mode === 'row',
|
||||||
|
'max-w-xs w-[18rem] shrink-0 flex flex-col': mode === 'column',
|
||||||
|
}"
|
||||||
|
:to="to"
|
||||||
|
:isInternal="isInternal"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-secondary rounded-lg"
|
||||||
|
:class="{ 'sm:w-full sm:max-w-[20rem]': mode === 'row' }"
|
||||||
>
|
>
|
||||||
<div class="bg-secondary rounded-lg">
|
|
||||||
<figure class="block relative pt-40">
|
<figure class="block relative pt-40">
|
||||||
<lazy-image-wrapper
|
<lazy-image-wrapper
|
||||||
:picture="event.picture"
|
:picture="event.picture"
|
||||||
style="height: 100%; position: absolute; top: 0; left: 0; width: 100%"
|
style="height: 100%; position: absolute; top: 0; left: 0; width: 100%"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="absolute top-3 right-0 ltr:-mr-1 rtl:-ml-1 z-10 max-w-xs no-underline flex flex-col gap-1"
|
class="absolute top-3 right-0 ltr:-mr-1 rtl:-ml-1 z-10 max-w-xs no-underline flex flex-col gap-1 items-end"
|
||||||
|
v-show="mode === 'column'"
|
||||||
v-if="event.tags || event.status !== EventStatus.CONFIRMED"
|
v-if="event.tags || event.status !== EventStatus.CONFIRMED"
|
||||||
>
|
>
|
||||||
<mobilizon-tag
|
<mobilizon-tag
|
||||||
@ -30,30 +39,39 @@
|
|||||||
v-for="tag in (event.tags || []).slice(0, 3)"
|
v-for="tag in (event.tags || []).slice(0, 3)"
|
||||||
:key="tag.slug"
|
:key="tag.slug"
|
||||||
>
|
>
|
||||||
<mobilizon-tag dir="auto">{{ tag.title }}</mobilizon-tag>
|
<mobilizon-tag dir="auto" :with-hash-tag="true">{{
|
||||||
|
tag.title
|
||||||
|
}}</mobilizon-tag>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2">
|
<div class="p-2 flex-auto" :class="{ 'sm:flex-1': mode === 'row' }">
|
||||||
<div class="relative flex flex-col h-full">
|
<div class="relative flex flex-col h-full">
|
||||||
<div class="-mt-3 h-0 flex mb-3 ltr:ml-0 rtl:mr-0 items-end self-start">
|
<div
|
||||||
|
class="-mt-3 h-0 flex mb-3 ltr:ml-0 rtl:mr-0 items-end self-start"
|
||||||
|
:class="{ 'sm:hidden': mode === 'row' }"
|
||||||
|
>
|
||||||
<date-calendar-icon
|
<date-calendar-icon
|
||||||
:small="true"
|
:small="true"
|
||||||
v-if="!mergedOptions.hideDate"
|
v-if="!mergedOptions.hideDate"
|
||||||
:date="event.beginsOn.toString()"
|
:date="event.beginsOn.toString()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex flex-col justify-between">
|
<span
|
||||||
<h3
|
class="text-gray-700 dark:text-white font-semibold hidden"
|
||||||
class="text-lg leading-5 line-clamp-3 font-bold text-violet-3 dark:text-white"
|
:class="{ 'sm:block': mode === 'row' }"
|
||||||
:title="event.title"
|
>{{ formatDateTimeWithCurrentLocale }}</span
|
||||||
|
>
|
||||||
|
<div class="w-full flex flex-col justify-between h-full">
|
||||||
|
<h2
|
||||||
|
class="mt-0 mb-2 text-2xl line-clamp-3 font-bold text-violet-3 dark:text-white"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
:lang="event.language"
|
:lang="event.language"
|
||||||
>
|
>
|
||||||
{{ event.title }}
|
{{ event.title }}
|
||||||
</h3>
|
</h2>
|
||||||
<div class="pt-3">
|
<div class="">
|
||||||
<div
|
<div
|
||||||
class="flex items-center text-violet-3 dark:text-white"
|
class="flex items-center text-violet-3 dark:text-white"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
@ -68,7 +86,7 @@
|
|||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
<account-circle v-else />
|
<account-circle v-else />
|
||||||
<span class="text-sm font-semibold ltr:pl-2 rtl:pr-2">
|
<span class="font-semibold ltr:pl-2 rtl:pr-2">
|
||||||
{{ organizerDisplayName(event) }}
|
{{ organizerDisplayName(event) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -84,11 +102,38 @@
|
|||||||
<Video />
|
<Video />
|
||||||
<span class="ltr:pl-2 rtl:pr-2">{{ $t("Online") }}</span>
|
<span class="ltr:pl-2 rtl:pr-2">{{ $t("Online") }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div
|
||||||
</div>
|
class="mt-1 no-underline gap-1 items-center hidden"
|
||||||
</div>
|
:class="{ 'sm:flex': mode === 'row' }"
|
||||||
</div>
|
v-if="event.tags || event.status !== EventStatus.CONFIRMED"
|
||||||
|
>
|
||||||
|
<mobilizon-tag
|
||||||
|
variant="info"
|
||||||
|
v-if="event.status === EventStatus.TENTATIVE"
|
||||||
|
>
|
||||||
|
{{ $t("Tentative") }}
|
||||||
|
</mobilizon-tag>
|
||||||
|
<mobilizon-tag
|
||||||
|
variant="danger"
|
||||||
|
v-if="event.status === EventStatus.CANCELLED"
|
||||||
|
>
|
||||||
|
{{ $t("Cancelled") }}
|
||||||
|
</mobilizon-tag>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: RouteName.TAG, params: { tag: tag.title } }"
|
||||||
|
v-for="tag in (event.tags || []).slice(0, 3)"
|
||||||
|
:key="tag.slug"
|
||||||
|
>
|
||||||
|
<mobilizon-tag :with-hash-tag="true" dir="auto">{{
|
||||||
|
tag.title
|
||||||
|
}}</mobilizon-tag>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LinkOrRouterLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -104,17 +149,29 @@ import { EventStatus } from "@/types/enums";
|
|||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
import InlineAddress from "@/components/Address/InlineAddress.vue";
|
import InlineAddress from "@/components/Address/InlineAddress.vue";
|
||||||
|
|
||||||
import { computed } from "vue";
|
import { computed, inject } from "vue";
|
||||||
import MobilizonTag from "../Tag.vue";
|
import MobilizonTag from "@/components/Tag.vue";
|
||||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||||
import Video from "vue-material-design-icons/Video.vue";
|
import Video from "vue-material-design-icons/Video.vue";
|
||||||
|
import { formatDateTimeForEvent } from "@/utils/datetime";
|
||||||
|
import type { Locale } from "date-fns";
|
||||||
|
import LinkOrRouterLink from "../core/LinkOrRouterLink.vue";
|
||||||
|
|
||||||
const props = defineProps<{ event: IEvent; options?: IEventCardOptions }>();
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
event: IEvent;
|
||||||
|
options?: IEventCardOptions;
|
||||||
|
mode?: "row" | "column";
|
||||||
|
}>(),
|
||||||
|
{ mode: "column" }
|
||||||
|
);
|
||||||
const defaultOptions: IEventCardOptions = {
|
const defaultOptions: IEventCardOptions = {
|
||||||
hideDate: false,
|
hideDate: false,
|
||||||
loggedPerson: false,
|
loggedPerson: false,
|
||||||
hideDetails: false,
|
hideDetails: false,
|
||||||
organizerActor: null,
|
organizerActor: null,
|
||||||
|
isRemoteEvent: false,
|
||||||
|
isLoggedIn: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mergedOptions = computed<IEventCardOptions>(() => ({
|
const mergedOptions = computed<IEventCardOptions>(() => ({
|
||||||
@ -132,4 +189,31 @@ const mergedOptions = computed<IEventCardOptions>(() => ({
|
|||||||
const actorAvatarURL = computed<string | null>(() =>
|
const actorAvatarURL = computed<string | null>(() =>
|
||||||
organizerAvatarUrl(props.event)
|
organizerAvatarUrl(props.event)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dateFnsLocale = inject<Locale>("dateFnsLocale");
|
||||||
|
|
||||||
|
const formatDateTimeWithCurrentLocale = computed(() => {
|
||||||
|
if (!dateFnsLocale) return;
|
||||||
|
return formatDateTimeForEvent(new Date(props.event.beginsOn), dateFnsLocale);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isInternal = computed(() => {
|
||||||
|
return (
|
||||||
|
mergedOptions.value.isRemoteEvent &&
|
||||||
|
mergedOptions.value.isLoggedIn === false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const to = computed(() => {
|
||||||
|
if (mergedOptions.value.isRemoteEvent) {
|
||||||
|
if (mergedOptions.value.isLoggedIn === false) {
|
||||||
|
return props.event.url;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: RouteName.INTERACT,
|
||||||
|
query: { uri: encodeURI(props.event.url) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { name: RouteName.EVENT, params: { uuid: props.event.uuid } };
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p v-else-if="isSameDay() && showStartTime && showEndTime">
|
<p v-else-if="isSameDay() && showStartTime && showEndTime">
|
||||||
<span>{{
|
<span>{{
|
||||||
$t("On {date} from {startTime} to {endTime}", {
|
t("On {date} from {startTime} to {endTime}", {
|
||||||
date: formatDate(beginsOn),
|
date: formatDate(beginsOn),
|
||||||
startTime: formatTime(beginsOn, timezoneToShow),
|
startTime: formatTime(beginsOn, timezoneToShow),
|
||||||
endTime: formatTime(endsOn, timezoneToShow),
|
endTime: formatTime(endsOn, timezoneToShow),
|
||||||
@ -31,27 +31,24 @@
|
|||||||
</p>
|
</p>
|
||||||
<p v-else-if="isSameDay() && showStartTime && !showEndTime">
|
<p v-else-if="isSameDay() && showStartTime && !showEndTime">
|
||||||
{{
|
{{
|
||||||
$t("On {date} starting at {startTime}", {
|
t("On {date} starting at {startTime}", {
|
||||||
date: formatDate(beginsOn),
|
date: formatDate(beginsOn),
|
||||||
startTime: formatTime(beginsOn),
|
startTime: formatTime(beginsOn),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
<p v-else-if="isSameDay()">
|
<p v-else-if="isSameDay()">
|
||||||
{{ $t("On {date}", { date: formatDate(beginsOn) }) }}
|
{{ t("On {date}", { date: formatDate(beginsOn) }) }}
|
||||||
</p>
|
</p>
|
||||||
<p v-else-if="endsOn && showStartTime && showEndTime">
|
<p v-else-if="endsOn && showStartTime && showEndTime">
|
||||||
<span>
|
<span>
|
||||||
{{
|
{{
|
||||||
$t(
|
t("From the {startDate} at {startTime} to the {endDate} at {endTime}", {
|
||||||
"From the {startDate} at {startTime} to the {endDate} at {endTime}",
|
|
||||||
{
|
|
||||||
startDate: formatDate(beginsOn),
|
startDate: formatDate(beginsOn),
|
||||||
startTime: formatTime(beginsOn, timezoneToShow),
|
startTime: formatTime(beginsOn, timezoneToShow),
|
||||||
endDate: formatDate(endsOn),
|
endDate: formatDate(endsOn),
|
||||||
endTime: formatTime(endsOn, timezoneToShow),
|
endTime: formatTime(endsOn, timezoneToShow),
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
@ -66,7 +63,7 @@
|
|||||||
<p v-else-if="endsOn && showStartTime">
|
<p v-else-if="endsOn && showStartTime">
|
||||||
<span>
|
<span>
|
||||||
{{
|
{{
|
||||||
$t("From the {startDate} at {startTime} to the {endDate}", {
|
t("From the {startDate} at {startTime} to the {endDate}", {
|
||||||
startDate: formatDate(beginsOn),
|
startDate: formatDate(beginsOn),
|
||||||
startTime: formatTime(beginsOn, timezoneToShow),
|
startTime: formatTime(beginsOn, timezoneToShow),
|
||||||
endDate: formatDate(endsOn),
|
endDate: formatDate(endsOn),
|
||||||
@ -169,22 +166,22 @@ const differentFromUserTimezone = computed((): boolean => {
|
|||||||
const singleTimeZone = computed((): string => {
|
const singleTimeZone = computed((): string => {
|
||||||
if (showLocalTimezone.value) {
|
if (showLocalTimezone.value) {
|
||||||
return t("Local time ({timezone})", {
|
return t("Local time ({timezone})", {
|
||||||
timezone: timezoneToShow,
|
timezone: timezoneToShow.value,
|
||||||
}) as string;
|
});
|
||||||
}
|
}
|
||||||
return t("Time in your timezone ({timezone})", {
|
return t("Time in your timezone ({timezone})", {
|
||||||
timezone: timezoneToShow,
|
timezone: timezoneToShow.value,
|
||||||
}) as string;
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const multipleTimeZones = computed((): string => {
|
const multipleTimeZones = computed((): string => {
|
||||||
if (showLocalTimezone.value) {
|
if (showLocalTimezone.value) {
|
||||||
return t("Local time ({timezone})", {
|
return t("Local times ({timezone})", {
|
||||||
timezone: timezoneToShow,
|
timezone: timezoneToShow.value,
|
||||||
}) as string;
|
});
|
||||||
}
|
}
|
||||||
return t("Times in your timezone ({timezone})", {
|
return t("Times in your timezone ({timezone})", {
|
||||||
timezone: timezoneToShow,
|
timezone: timezoneToShow.value,
|
||||||
}) as string;
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -87,7 +87,7 @@ const RoutingParamType = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MapLeaflet = import("../../components/Map.vue");
|
const MapLeaflet = import("@/components/LeafletMap.vue");
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
address: IAddress;
|
address: IAddress;
|
||||||
|
@ -136,10 +136,10 @@ const metadata = computed({
|
|||||||
};
|
};
|
||||||
}) as any[];
|
}) as any[];
|
||||||
},
|
},
|
||||||
set(metadata: IEventMetadataDescription[]) {
|
set(newMetadata: IEventMetadataDescription[]) {
|
||||||
emit(
|
emit(
|
||||||
"update:modelValue",
|
"update:modelValue",
|
||||||
metadata.filter((elem) => elem)
|
newMetadata.filter((elem) => elem)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<div class="address" v-if="physicalAddress">
|
<div class="address" v-if="physicalAddress">
|
||||||
<address-info :address="physicalAddress" />
|
<address-info :address="physicalAddress" />
|
||||||
<o-button
|
<o-button
|
||||||
type="is-text"
|
variant="text"
|
||||||
class="map-show-button"
|
class="map-show-button"
|
||||||
@click="$emit('showMapModal', true)"
|
@click="$emit('showMapModal', true)"
|
||||||
v-if="physicalAddress.geom"
|
v-if="physicalAddress.geom"
|
||||||
|
@ -22,27 +22,23 @@
|
|||||||
:lang="event.language"
|
:lang="event.language"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
<b-tag
|
<tag
|
||||||
variant="info"
|
variant="info"
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
v-if="event.status === EventStatus.TENTATIVE"
|
v-if="event.status === EventStatus.TENTATIVE"
|
||||||
>
|
>
|
||||||
{{ $t("Tentative") }}
|
{{ $t("Tentative") }}
|
||||||
</b-tag>
|
</tag>
|
||||||
<b-tag
|
<tag
|
||||||
variant="danger"
|
variant="danger"
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
v-if="event.status === EventStatus.CANCELLED"
|
v-if="event.status === EventStatus.CANCELLED"
|
||||||
>
|
>
|
||||||
{{ $t("Cancelled") }}
|
{{ $t("Cancelled") }}
|
||||||
</b-tag>
|
</tag>
|
||||||
<b-tag
|
<tag class="mr-2" variant="warning" size="medium" v-if="event.draft">{{
|
||||||
class="mr-2"
|
$t("Draft")
|
||||||
variant="warning"
|
}}</tag>
|
||||||
size="is-medium"
|
|
||||||
v-if="event.draft"
|
|
||||||
>{{ $t("Draft") }}</b-tag
|
|
||||||
>
|
|
||||||
{{ event.title }}
|
{{ event.title }}
|
||||||
</h3>
|
</h3>
|
||||||
<inline-address
|
<inline-address
|
||||||
@ -99,7 +95,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span v-if="event.participantStats.notApproved > 0">
|
<span v-if="event.participantStats.notApproved > 0">
|
||||||
<o-button
|
<o-button
|
||||||
type="is-text"
|
variant="text"
|
||||||
@click="
|
@click="
|
||||||
gotToWithCheck(participation, {
|
gotToWithCheck(participation, {
|
||||||
name: RouteName.PARTICIPATIONS,
|
name: RouteName.PARTICIPATIONS,
|
||||||
@ -134,6 +130,7 @@ import InlineAddress from "@/components/Address/InlineAddress.vue";
|
|||||||
import Video from "vue-material-design-icons/Video.vue";
|
import Video from "vue-material-design-icons/Video.vue";
|
||||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||||
import AccountMultiple from "vue-material-design-icons/AccountMultiple.vue";
|
import AccountMultiple from "vue-material-design-icons/AccountMultiple.vue";
|
||||||
|
import Tag from "@/components/Tag.vue";
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<article class="bg-white dark:bg-mbz-purple mb-5 mt-4 pb-2 md:p-0">
|
<article
|
||||||
<div class="bg-yellow-2 flex p-2 text-violet-title rounded-t-lg" dir="auto">
|
class="bg-white dark:bg-mbz-purple dark:hover:bg-mbz-purple-400 mb-5 mt-4 pb-2 md:p-0 rounded-t-lg"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-mbz-yellow-alt-100 flex p-2 text-violet-title rounded-t-lg"
|
||||||
|
dir="auto"
|
||||||
|
>
|
||||||
<figure
|
<figure
|
||||||
class="image is-24x24 ltr:pr-1 rtl:pl-1"
|
class="image is-24x24 ltr:pr-1 rtl:pl-1"
|
||||||
v-if="participation.actor.avatar"
|
v-if="participation.actor.avatar"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="is-rounded"
|
class="rounded"
|
||||||
:src="participation.actor.avatar.url"
|
:src="participation.actor.avatar.url"
|
||||||
alt=""
|
alt=""
|
||||||
height="24"
|
height="24"
|
||||||
@ -157,7 +162,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<o-button
|
<o-button
|
||||||
v-if="participation.event.participantStats.notApproved > 0"
|
v-if="participation.event.participantStats.notApproved > 0"
|
||||||
type="is-text"
|
variant="text"
|
||||||
@click="
|
@click="
|
||||||
gotToWithCheck(participation, {
|
gotToWithCheck(participation, {
|
||||||
name: RouteName.PARTICIPATIONS,
|
name: RouteName.PARTICIPATIONS,
|
||||||
@ -330,7 +335,7 @@ const defaultOptions: IEventCardOptions = {
|
|||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
participation: IParticipant;
|
participation: IParticipant;
|
||||||
options: IEventCardOptions;
|
options?: IEventCardOptions;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
options: () => ({
|
options: () => ({
|
||||||
|
@ -7,14 +7,11 @@
|
|||||||
:message="fieldErrors"
|
:message="fieldErrors"
|
||||||
:type="{ 'is-danger': fieldErrors }"
|
:type="{ 'is-danger': fieldErrors }"
|
||||||
class="!-mt-2"
|
class="!-mt-2"
|
||||||
|
:labelClass="labelClass"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{ actualLabel }}
|
{{ actualLabel }}
|
||||||
<span
|
<span v-if="gettingLocation">{{ t("Getting location") }}</span>
|
||||||
class="is-size-6 has-text-weight-normal"
|
|
||||||
v-if="gettingLocation"
|
|
||||||
>{{ t("Getting location") }}</span
|
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
<p class="control" v-if="canShowLocateMeButton">
|
<p class="control" v-if="canShowLocateMeButton">
|
||||||
<o-loading
|
<o-loading
|
||||||
@ -54,7 +51,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<span v-if="isFetching">{{ t("Searching…") }}</span>
|
<span v-if="isFetching">{{ t("Searching…") }}</span>
|
||||||
<div v-else-if="queryText.length >= 3" class="is-enabled">
|
<div v-else-if="queryText.length >= 3" class="enabled">
|
||||||
<span>{{
|
<span>{{
|
||||||
t('No results for "{queryText}"', { queryText })
|
t('No results for "{queryText}"', { queryText })
|
||||||
}}</span>
|
}}</span>
|
||||||
@ -121,12 +118,16 @@ import { useGeocodingAutocomplete } from "@/composition/apollo/config";
|
|||||||
import { ADDRESS } from "@/graphql/address";
|
import { ADDRESS } from "@/graphql/address";
|
||||||
import { useReverseGeocode } from "@/composition/apollo/address";
|
import { useReverseGeocode } from "@/composition/apollo/address";
|
||||||
import { useLazyQuery } from "@vue/apollo-composable";
|
import { useLazyQuery } from "@vue/apollo-composable";
|
||||||
const MapLeaflet = defineAsyncComponent(() => import("../Map.vue"));
|
const MapLeaflet = defineAsyncComponent(
|
||||||
|
() => import("@/components/LeafletMap.vue")
|
||||||
|
);
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
modelValue: IAddress | null;
|
modelValue: IAddress | null;
|
||||||
|
defaultText?: string | null;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
labelClass?: string;
|
||||||
userTimezone?: string;
|
userTimezone?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
hideMap?: boolean;
|
hideMap?: boolean;
|
||||||
@ -134,7 +135,8 @@ const props = withDefaults(
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
label: "",
|
labelClass: "",
|
||||||
|
defaultText: "",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
hideMap: false,
|
hideMap: false,
|
||||||
hideSelected: false,
|
hideSelected: false,
|
||||||
@ -204,7 +206,7 @@ const checkCurrentPosition = (e: LatLng): boolean => {
|
|||||||
const { t, locale } = useI18n({ useScope: "global" });
|
const { t, locale } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
const actualLabel = computed((): string => {
|
const actualLabel = computed((): string => {
|
||||||
return props.label ?? (t("Find an address") as string);
|
return props.label ?? t("Find an address");
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
@ -253,11 +255,14 @@ const asyncData = async (query: string): Promise<void> => {
|
|||||||
|
|
||||||
const queryText = computed({
|
const queryText = computed({
|
||||||
get() {
|
get() {
|
||||||
return selected.value ? addressFullName(selected.value) : "";
|
return (
|
||||||
|
(selected.value ? addressFullName(selected.value) : props.defaultText) ??
|
||||||
|
""
|
||||||
|
);
|
||||||
},
|
},
|
||||||
set(text) {
|
set(text) {
|
||||||
if (text === "" && selected.value?.id) {
|
if (text === "" && selected.value?.id) {
|
||||||
console.log("doing reset");
|
console.debug("doing reset");
|
||||||
resetAddress();
|
resetAddress();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="events-wrapper">
|
<div class="events-wrapper">
|
||||||
<div class="flex flex-col gap-4" v-for="key of keys" :key="key">
|
<div class="flex flex-col gap-4" v-for="key of keys" :key="key">
|
||||||
<h2 class="is-size-5 month-name">
|
<h2 class="month-name">
|
||||||
{{ monthName(groupEvents(key)[0]) }}
|
{{ monthName(groupEvents(key)[0]) }}
|
||||||
</h2>
|
</h2>
|
||||||
<event-minimalist-card
|
<event-minimalist-card
|
||||||
|
@ -27,10 +27,6 @@ const videoDetails = computed((): { host: string; uuid: string } | null => {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const origin = computed((): string => {
|
|
||||||
return window.location.hostname;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.peertube {
|
.peertube {
|
@ -28,10 +28,6 @@ const videoID = computed((): string | null => {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const origin = computed((): string => {
|
|
||||||
return window.location.hostname;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.youtube {
|
.youtube {
|
@ -34,9 +34,9 @@
|
|||||||
class="flex flex-wrap p-3 bg-white hover:bg-gray-50 dark:bg-violet-3 dark:hover:bg-violet-3/60 border border-gray-300 rounded-lg cursor-pointer peer-checked:ring-primary peer-checked:ring-2 peer-checked:border-transparent"
|
class="flex flex-wrap p-3 bg-white hover:bg-gray-50 dark:bg-violet-3 dark:hover:bg-violet-3/60 border border-gray-300 rounded-lg cursor-pointer peer-checked:ring-primary peer-checked:ring-2 peer-checked:border-transparent"
|
||||||
:for="`availableActor-${availableActor?.id}`"
|
:for="`availableActor-${availableActor?.id}`"
|
||||||
>
|
>
|
||||||
<figure class="" v-if="availableActor?.avatar">
|
<figure class="h-12 w-12" v-if="availableActor?.avatar">
|
||||||
<img
|
<img
|
||||||
class="rounded"
|
class="rounded-full h-full w-full object-cover"
|
||||||
:src="availableActor.avatar.url"
|
:src="availableActor.avatar.url"
|
||||||
alt=""
|
alt=""
|
||||||
width="48"
|
width="48"
|
||||||
|
@ -12,9 +12,9 @@
|
|||||||
>
|
>
|
||||||
<div class="flex gap-1 p-4">
|
<div class="flex gap-1 p-4">
|
||||||
<div class="">
|
<div class="">
|
||||||
<figure class="" v-if="selectedActor.avatar">
|
<figure class="h-12 w-12" v-if="selectedActor.avatar">
|
||||||
<img
|
<img
|
||||||
class="rounded"
|
class="rounded-full h-full w-full object-cover"
|
||||||
:src="selectedActor.avatar.url"
|
:src="selectedActor.avatar.url"
|
||||||
:alt="selectedActor.avatar.alt ?? ''"
|
:alt="selectedActor.avatar.alt ?? ''"
|
||||||
height="48"
|
height="48"
|
||||||
@ -207,7 +207,7 @@ const props = withDefaults(
|
|||||||
{ inline: true, contacts: () => [] }
|
{ inline: true, contacts: () => [] }
|
||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue", "update:Contacts"]);
|
const emit = defineEmits(["update:modelValue", "update:contacts"]);
|
||||||
|
|
||||||
const selectedActor = computed({
|
const selectedActor = computed({
|
||||||
get(): IActor | undefined {
|
get(): IActor | undefined {
|
||||||
@ -252,7 +252,7 @@ const actualContacts = computed({
|
|||||||
},
|
},
|
||||||
set(contactsIds: (string | undefined)[]) {
|
set(contactsIds: (string | undefined)[]) {
|
||||||
emit(
|
emit(
|
||||||
"update:Contacts",
|
"update:contacts",
|
||||||
actorMembers.value.filter(({ id }) => contactsIds.includes(id))
|
actorMembers.value.filter(({ id }) => contactsIds.includes(id))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -68,7 +68,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { IActor, IPerson } from "@/types/actor";
|
import { IPerson } from "@/types/actor";
|
||||||
import { EventJoinOptions, ParticipantRole } from "@/types/enums";
|
import { EventJoinOptions, ParticipantRole } from "@/types/enums";
|
||||||
import { IEvent } from "@/types/event.model";
|
import { IEvent } from "@/types/event.model";
|
||||||
import ParticipationButton from "./ParticipationButton.vue";
|
import ParticipationButton from "./ParticipationButton.vue";
|
||||||
|
@ -37,7 +37,7 @@ import { useI18n } from "vue-i18n";
|
|||||||
import { IEvent } from "@/types/event.model";
|
import { IEvent } from "@/types/event.model";
|
||||||
import ShareModal from "@/components/Share/ShareModal.vue";
|
import ShareModal from "@/components/Share/ShareModal.vue";
|
||||||
|
|
||||||
const props = withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
event: IEvent;
|
event: IEvent;
|
||||||
eventCapacityOK?: boolean;
|
eventCapacityOK?: boolean;
|
||||||
|
@ -16,8 +16,8 @@ import TagInput from "./TagInput.vue";
|
|||||||
|
|
||||||
const tags = reactive<ITag[]>([{ title: "Hello", slug: "hello" }]);
|
const tags = reactive<ITag[]>([{ title: "Hello", slug: "hello" }]);
|
||||||
|
|
||||||
const fetchTags = async (text: string) =>
|
const fetchTags = async () =>
|
||||||
new Promise<ITag[]>((resolve, reject) => {
|
new Promise<ITag[]>((resolve) => {
|
||||||
resolve([{ title: "Welcome", slug: "welcome" }]);
|
resolve([{ title: "Welcome", slug: "welcome" }]);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<template #label>
|
<template #label>
|
||||||
{{ $t("Add some tags") }}
|
{{ $t("Add some tags") }}
|
||||||
<o-tooltip
|
<o-tooltip
|
||||||
type="dark"
|
variant="dark"
|
||||||
:label="
|
:label="
|
||||||
$t('You can add tags by hitting the Enter key or by adding a comma')
|
$t('You can add tags by hitting the Enter key or by adding a comma')
|
||||||
"
|
"
|
||||||
@ -77,9 +77,9 @@ const tagsStrings = computed({
|
|||||||
get(): string[] {
|
get(): string[] {
|
||||||
return props.modelValue.map((tag: ITag) => tag.title);
|
return props.modelValue.map((tag: ITag) => tag.title);
|
||||||
},
|
},
|
||||||
set(tagsStrings: string[]) {
|
set(newTagsStrings: string[]) {
|
||||||
console.debug("tagsStrings", tagsStrings);
|
console.debug("tagsStrings", newTagsStrings);
|
||||||
const tagEntities = tagsStrings.map((tag: string | ITag) => {
|
const tagEntities = newTagsStrings.map((tag: string | ITag) => {
|
||||||
if (typeof tag !== "string") {
|
if (typeof tag !== "string") {
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
@ -15,14 +15,17 @@
|
|||||||
<GroupCard :group="groupWithFollowersOrMembers" />
|
<GroupCard :group="groupWithFollowersOrMembers" />
|
||||||
</div>
|
</div>
|
||||||
</Variant>
|
</Variant>
|
||||||
|
<Variant title="Row mode">
|
||||||
|
<GroupCard :group="groupWithFollowersOrMembers" mode="row" />
|
||||||
|
</Variant>
|
||||||
</Story>
|
</Story>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { IActor } from "@/types/actor";
|
import { IGroup } from "@/types/actor";
|
||||||
import GroupCard from "./GroupCard.vue";
|
import GroupCard from "./GroupCard.vue";
|
||||||
|
|
||||||
const basicGroup: IActor = {
|
const basicGroup: IGroup = {
|
||||||
name: "Framasoft",
|
name: "Framasoft",
|
||||||
preferredUsername: "framasoft",
|
preferredUsername: "framasoft",
|
||||||
avatar: null,
|
avatar: null,
|
||||||
@ -34,7 +37,7 @@ const basicGroup: IActor = {
|
|||||||
followers: { total: 0, elements: [] },
|
followers: { total: 0, elements: [] },
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupWithMedia = {
|
const groupWithMedia: IGroup = {
|
||||||
...basicGroup,
|
...basicGroup,
|
||||||
banner: {
|
banner: {
|
||||||
url: "https://mobilizon.fr/media/7b340fe641e7ad711ebb6f8821b5ce824992db08701e37ebb901c175436aaafc.jpg?name=framasoft%27s%20banner.jpg",
|
url: "https://mobilizon.fr/media/7b340fe641e7ad711ebb6f8821b5ce824992db08701e37ebb901c175436aaafc.jpg?name=framasoft%27s%20banner.jpg",
|
||||||
@ -44,9 +47,14 @@ const groupWithMedia = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupWithFollowersOrMembers = {
|
const groupWithFollowersOrMembers: IGroup = {
|
||||||
...groupWithMedia,
|
...groupWithMedia,
|
||||||
members: { total: 2, elements: [] },
|
members: { total: 2, elements: [] },
|
||||||
followers: { total: 5, elements: [] },
|
followers: { total: 5, elements: [] },
|
||||||
|
summary:
|
||||||
|
"You can also use variant modifiers to target media queries like responsive breakpoints, dark mode, prefers-reduced-motion, and more. For example, use md:h-full to apply the h-full utility at only medium screen sizes and above.",
|
||||||
|
physicalAddress: {
|
||||||
|
description: "Nantes",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,29 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-link
|
<LinkOrRouterLink
|
||||||
:to="{
|
:to="to"
|
||||||
name: RouteName.GROUP,
|
:isInternal="isInternal"
|
||||||
params: { preferredUsername: usernameWithDomain(group) },
|
class="mbz-card shrink-0 dark:bg-mbz-purple dark:text-white rounded-lg shadow-lg my-4 flex items-center flex-col"
|
||||||
|
:class="{
|
||||||
|
'sm:flex-row': mode === 'row',
|
||||||
|
'max-w-xs w-[18rem] shrink-0 flex flex-col': mode === 'column',
|
||||||
}"
|
}"
|
||||||
class="card flex flex-col shrink-0 w-[18rem] bg-white dark:bg-mbz-purple dark:text-white rounded-lg shadow-lg"
|
|
||||||
>
|
>
|
||||||
<figure class="rounded-t-lg flex justify-center h-40">
|
<div class="flex-none p-2 md:p-4">
|
||||||
<lazy-image-wrapper :picture="group.banner" :rounded="true" />
|
|
||||||
</figure>
|
|
||||||
<div class="py-2 pl-2">
|
|
||||||
<div class="flex gap-1 mb-2">
|
|
||||||
<div class="">
|
|
||||||
<figure class="" v-if="group.avatar">
|
<figure class="" v-if="group.avatar">
|
||||||
<img
|
<img
|
||||||
class="rounded-xl"
|
class="rounded-full"
|
||||||
:src="group.avatar.url"
|
:src="group.avatar.url"
|
||||||
alt=""
|
alt=""
|
||||||
height="64"
|
height="128"
|
||||||
width="64"
|
width="128"
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
<AccountGroup v-else :size="64" />
|
<AccountGroup v-else :size="128" />
|
||||||
</div>
|
</div>
|
||||||
<div class="px-1 overflow-hidden">
|
<div
|
||||||
|
class="py-2 px-2 md:px-4 flex flex-col h-full justify-between w-full"
|
||||||
|
:class="{ 'sm:flex-1': mode === 'row' }"
|
||||||
|
>
|
||||||
|
<div class="flex gap-1 mb-2">
|
||||||
|
<div class="px-1 overflow-hidden flex-auto">
|
||||||
<h3
|
<h3
|
||||||
class="text-2xl leading-5 line-clamp-3 font-bold text-violet-3 dark:text-white"
|
class="text-2xl leading-5 line-clamp-3 font-bold text-violet-3 dark:text-white"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
@ -46,7 +48,10 @@
|
|||||||
v-if="group.physicalAddress && addressFullName(group.physicalAddress)"
|
v-if="group.physicalAddress && addressFullName(group.physicalAddress)"
|
||||||
:physicalAddress="group.physicalAddress"
|
:physicalAddress="group.physicalAddress"
|
||||||
/>
|
/>
|
||||||
<p class="flex gap-1">
|
<p
|
||||||
|
class="flex gap-1"
|
||||||
|
v-if="group?.members?.total && group?.followers?.total"
|
||||||
|
>
|
||||||
<Account />
|
<Account />
|
||||||
{{
|
{{
|
||||||
t(
|
t(
|
||||||
@ -58,14 +63,28 @@
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
|
<p
|
||||||
|
class="flex gap-1"
|
||||||
|
v-else-if="group?.membersCount || group?.followersCount"
|
||||||
|
>
|
||||||
|
<Account />
|
||||||
|
{{
|
||||||
|
t(
|
||||||
|
"{count} members or followers",
|
||||||
|
{
|
||||||
|
count: (group.membersCount ?? 0) + (group.followersCount ?? 0),
|
||||||
|
},
|
||||||
|
(group.membersCount ?? 0) + (group.followersCount ?? 0)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</LinkOrRouterLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { displayName, IGroup, usernameWithDomain } from "@/types/actor";
|
import { displayName, IGroup, usernameWithDomain } from "@/types/actor";
|
||||||
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
|
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
import InlineAddress from "@/components/Address/InlineAddress.vue";
|
import InlineAddress from "@/components/Address/InlineAddress.vue";
|
||||||
import { addressFullName } from "@/types/address.model";
|
import { addressFullName } from "@/types/address.model";
|
||||||
@ -74,16 +93,40 @@ import AccountGroup from "vue-material-design-icons/AccountGroup.vue";
|
|||||||
import Account from "vue-material-design-icons/Account.vue";
|
import Account from "vue-material-design-icons/Account.vue";
|
||||||
import { htmlToText } from "@/utils/html";
|
import { htmlToText } from "@/utils/html";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
import LinkOrRouterLink from "../core/LinkOrRouterLink.vue";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
group: IGroup;
|
group: IGroup;
|
||||||
showSummary: boolean;
|
showSummary?: boolean;
|
||||||
|
isRemoteGroup?: boolean;
|
||||||
|
isLoggedIn?: boolean;
|
||||||
|
mode?: "row" | "column";
|
||||||
}>(),
|
}>(),
|
||||||
{ showSummary: true }
|
{ showSummary: true, isRemoteGroup: false, isLoggedIn: true, mode: "column" }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
const saneSummary = computed(() => htmlToText(props.group.summary));
|
const saneSummary = computed(() => htmlToText(props.group.summary ?? ""));
|
||||||
|
|
||||||
|
const isInternal = computed(() => {
|
||||||
|
return props.isRemoteGroup && props.isLoggedIn === false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const to = computed(() => {
|
||||||
|
if (props.isRemoteGroup) {
|
||||||
|
if (props.isLoggedIn === false) {
|
||||||
|
return props.group.url;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: RouteName.INTERACT,
|
||||||
|
query: { uri: encodeURI(props.group.url) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: RouteName.GROUP,
|
||||||
|
params: { preferredUsername: usernameWithDomain(props.group) },
|
||||||
|
};
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -73,19 +73,19 @@ const adminMember: IMember = {
|
|||||||
role: MemberRole.ADMINISTRATOR,
|
role: MemberRole.ADMINISTRATOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupWithMedia = {
|
// const groupWithMedia = {
|
||||||
...basicGroup,
|
// ...basicGroup,
|
||||||
banner: {
|
// banner: {
|
||||||
url: "https://mobilizon.fr/media/7b340fe641e7ad711ebb6f8821b5ce824992db08701e37ebb901c175436aaafc.jpg?name=framasoft%27s%20banner.jpg",
|
// url: "https://mobilizon.fr/media/7b340fe641e7ad711ebb6f8821b5ce824992db08701e37ebb901c175436aaafc.jpg?name=framasoft%27s%20banner.jpg",
|
||||||
},
|
// },
|
||||||
avatar: {
|
// avatar: {
|
||||||
url: "https://mobilizon.fr/media/ff5b2d425fb73e17fcbb56a1a032359ee0b21453c11af59e103e783817a32fdf.png?name=framasoft%27s%20avatar.png",
|
// url: "https://mobilizon.fr/media/ff5b2d425fb73e17fcbb56a1a032359ee0b21453c11af59e103e783817a32fdf.png?name=framasoft%27s%20avatar.png",
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
|
|
||||||
const groupWithFollowersOrMembers = {
|
// const groupWithFollowersOrMembers = {
|
||||||
...groupWithMedia,
|
// ...groupWithMedia,
|
||||||
members: { total: 2, elements: [] },
|
// members: { total: 2, elements: [] },
|
||||||
followers: { total: 5, elements: [] },
|
// followers: { total: 5, elements: [] },
|
||||||
};
|
// };
|
||||||
</script>
|
</script>
|
||||||
|
@ -41,19 +41,19 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<h2 class="mt-0">{{ member.parent.name }}</h2>
|
<h2 class="mt-0">{{ member.parent.name }}</h2>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col items-start">
|
||||||
<span class="text-sm">{{
|
<span class="text-sm">{{
|
||||||
`@${usernameWithDomain(member.parent)}`
|
`@${usernameWithDomain(member.parent)}`
|
||||||
}}</span>
|
}}</span>
|
||||||
<tag
|
<tag
|
||||||
variant="info"
|
variant="info"
|
||||||
v-if="member.role === MemberRole.ADMINISTRATOR"
|
v-if="member.role === MemberRole.ADMINISTRATOR"
|
||||||
>{{ $t("Administrator") }}</tag
|
>{{ t("Administrator") }}</tag
|
||||||
>
|
>
|
||||||
<tag
|
<tag
|
||||||
variant="info"
|
variant="info"
|
||||||
v-else-if="member.role === MemberRole.MODERATOR"
|
v-else-if="member.role === MemberRole.MODERATOR"
|
||||||
>{{ $t("Moderator") }}</tag
|
>{{ t("Moderator") }}</tag
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
@ -77,7 +77,7 @@
|
|||||||
@click="emit('leave')"
|
@click="emit('leave')"
|
||||||
>
|
>
|
||||||
<ExitToApp />
|
<ExitToApp />
|
||||||
{{ $t("Leave") }}
|
{{ t("Leave") }}
|
||||||
</o-dropdown-item>
|
</o-dropdown-item>
|
||||||
</o-dropdown>
|
</o-dropdown>
|
||||||
</div>
|
</div>
|
||||||
@ -96,10 +96,13 @@ import AccountGroup from "vue-material-design-icons/AccountGroup.vue";
|
|||||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||||
import Tag from "@/components/Tag.vue";
|
import Tag from "@/components/Tag.vue";
|
||||||
import { htmlToText } from "@/utils/html";
|
import { htmlToText } from "@/utils/html";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
member: IMember;
|
member: IMember;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits(["leave"]);
|
const emit = defineEmits(["leave"]);
|
||||||
|
|
||||||
|
const { t } = useI18n({ useScope: "global" });
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card">
|
<div class="">
|
||||||
<div class="card-content media">
|
<div class="">
|
||||||
<div class="media-content">
|
<div class="">
|
||||||
<div class="prose dark:prose-invert">
|
<div class="prose dark:prose-invert">
|
||||||
<i18n-t
|
<i18n-t
|
||||||
tag="p"
|
tag="p"
|
||||||
@ -12,12 +12,18 @@
|
|||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
<div class="media subfield">
|
<div class="">
|
||||||
<div class="media-left">
|
<div class="">
|
||||||
<figure class="image is-48x48" v-if="member.parent.avatar">
|
<figure v-if="member.parent.avatar">
|
||||||
<img class="is-rounded" :src="member.parent.avatar.url" alt="" />
|
<img
|
||||||
|
class="rounded"
|
||||||
|
:src="member.parent.avatar.url"
|
||||||
|
alt=""
|
||||||
|
height="48"
|
||||||
|
width="48"
|
||||||
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
<o-icon v-else size="large" icon="account-group" />
|
<AccountGroup :size="48" v-else />
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content">
|
<div class="media-content">
|
||||||
<div class="level">
|
<div class="level">
|
||||||
@ -31,8 +37,8 @@
|
|||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<h3 class="is-size-5">{{ member.parent.name }}</h3>
|
<h3 class="">{{ member.parent.name }}</h3>
|
||||||
<p class="is-size-7 has-text-grey-dark">
|
<p class="">
|
||||||
<span v-if="member.parent.domain">
|
<span v-if="member.parent.domain">
|
||||||
{{
|
{{
|
||||||
`@${member.parent.preferredUsername}@${member.parent.domain}`
|
`@${member.parent.preferredUsername}@${member.parent.domain}`
|
||||||
@ -45,8 +51,8 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="level-right">
|
<div class="">
|
||||||
<div class="level-item">
|
<div class="">
|
||||||
<o-button
|
<o-button
|
||||||
variant="success"
|
variant="success"
|
||||||
@click="$emit('accept', member.id)"
|
@click="$emit('accept', member.id)"
|
||||||
@ -54,7 +60,7 @@
|
|||||||
{{ $t("Accept") }}
|
{{ $t("Accept") }}
|
||||||
</o-button>
|
</o-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="level-item">
|
<div class="">
|
||||||
<o-button
|
<o-button
|
||||||
variant="danger"
|
variant="danger"
|
||||||
@click="$emit('reject', member.id)"
|
@click="$emit('reject', member.id)"
|
||||||
@ -75,6 +81,7 @@
|
|||||||
import { usernameWithDomain } from "@/types/actor";
|
import { usernameWithDomain } from "@/types/actor";
|
||||||
import { IMember } from "@/types/actor/member.model";
|
import { IMember } from "@/types/actor/member.model";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
|
import AccountGroup from "vue-material-design-icons/AccountGroup.vue";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
member: IMember;
|
member: IMember;
|
||||||
|
18
js/src/components/Group/SkeletonGroupResult.vue
Normal file
18
js/src/components/Group/SkeletonGroupResult.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="bg-white dark:bg-slate-800 shadow rounded-md max-w-sm w-full mx-auto"
|
||||||
|
>
|
||||||
|
<div class="animate-pulse flex flex-col space-3-4 items-center">
|
||||||
|
<div
|
||||||
|
class="object-cover h-40 w-40 rounded-full bg-slate-700 p-2 md:p-4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex gap-3 flex self-start flex-col justify-between p-2 md:p-4 w-full"
|
||||||
|
>
|
||||||
|
<div class="h-5 bg-slate-700"></div>
|
||||||
|
<div class="h-3 bg-slate-700"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -28,8 +28,7 @@ import { CATEGORY_STATISTICS } from "@/graphql/statistics";
|
|||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import shuffle from "lodash/shuffle";
|
import shuffle from "lodash/shuffle";
|
||||||
import { categoriesWithPictures } from "../Categories/constants";
|
import { categoriesWithPictures } from "../Categories/constants";
|
||||||
import { IConfig } from "@/types/config.model";
|
import { useEventCategories } from "@/composition/apollo/config";
|
||||||
import { CONFIG } from "@/graphql/config";
|
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
@ -40,14 +39,10 @@ const categoryStats = computed(
|
|||||||
() => categoryStatsResult.value?.categoryStatistics ?? []
|
() => categoryStatsResult.value?.categoryStatistics ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
const { result: configResult } = useQuery<{ config: IConfig }>(CONFIG);
|
const { eventCategories } = useEventCategories();
|
||||||
|
|
||||||
const config = computed(() => configResult.value?.config);
|
|
||||||
|
|
||||||
const eventCategories = computed(() => config.value?.eventCategories ?? []);
|
|
||||||
|
|
||||||
const eventCategoryLabel = (categoryId: string): string | undefined => {
|
const eventCategoryLabel = (categoryId: string): string | undefined => {
|
||||||
return eventCategories.value.find(({ id }) => categoryId == id)?.label;
|
return eventCategories.value?.find(({ id }) => categoryId == id)?.label;
|
||||||
};
|
};
|
||||||
|
|
||||||
const promotedCategories = computed((): CategoryStatsModel[] => {
|
const promotedCategories = computed((): CategoryStatsModel[] => {
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<form
|
<form
|
||||||
id="search-anchor"
|
id="search-anchor"
|
||||||
class="container mx-auto my-3 px-2 flex flex-wrap flex-col sm:flex-row items-stretch gap-2 text-center items-center justify-center dark:text-slate-100"
|
class="container mx-auto my-3 flex flex-wrap flex-col sm:flex-row items-stretch gap-2 text-center items-center justify-center dark:text-slate-100"
|
||||||
role="search"
|
role="search"
|
||||||
@submit.prevent="emit('submit')"
|
@submit.prevent="submit"
|
||||||
>
|
>
|
||||||
|
<label class="sr-only" for="search_field_input">{{
|
||||||
|
t("Keyword, event title, group name, etc.")
|
||||||
|
}}</label>
|
||||||
<o-input
|
<o-input
|
||||||
class="flex-1"
|
|
||||||
v-model="search"
|
v-model="search"
|
||||||
:placeholder="t('Keyword, event title, group name, etc.')"
|
:placeholder="t('Keyword, event title, group name, etc.')"
|
||||||
|
id="search_field_input"
|
||||||
autofocus
|
autofocus
|
||||||
autocapitalize="off"
|
autocapitalize="off"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@ -21,8 +24,10 @@
|
|||||||
v-model="location"
|
v-model="location"
|
||||||
:hide-map="true"
|
:hide-map="true"
|
||||||
:hide-selected="true"
|
:hide-selected="true"
|
||||||
|
:default-text="locationDefaultText"
|
||||||
|
labelClass="sr-only"
|
||||||
/>
|
/>
|
||||||
<o-button type="submit" icon-left="magnify">
|
<o-button native-type="submit" icon-left="magnify">
|
||||||
<template v-if="search">{{ t("Go!") }}</template>
|
<template v-if="search">{{ t("Go!") }}</template>
|
||||||
<template v-else>{{ t("Explore!") }}</template>
|
<template v-else>{{ t("Explore!") }}</template>
|
||||||
</o-button>
|
</o-button>
|
||||||
@ -35,23 +40,28 @@ import { AddressSearchType } from "@/types/enums";
|
|||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
|
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import RouteName from "@/router/name";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
location: IAddress;
|
location: IAddress | null;
|
||||||
|
locationDefaultText?: string | null;
|
||||||
search: string;
|
search: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: "update:location", location: IAddress): void;
|
(event: "update:location", location: IAddress | null): void;
|
||||||
(event: "update:search", newSearch: string): void;
|
(event: "update:search", newSearch: string): void;
|
||||||
(event: "submit"): void;
|
(event: "submit"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const location = computed({
|
const location = computed({
|
||||||
get(): IAddress {
|
get(): IAddress | null {
|
||||||
return props.location;
|
return props.location;
|
||||||
},
|
},
|
||||||
set(newLocation: IAddress) {
|
set(newLocation: IAddress | null) {
|
||||||
emit("update:location", newLocation);
|
emit("update:location", newLocation);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -65,6 +75,25 @@ const search = computed({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
emit("submit");
|
||||||
|
const lat = location.value?.geom
|
||||||
|
? parseFloat(location.value?.geom?.split(";")?.[1])
|
||||||
|
: undefined;
|
||||||
|
const lon = location.value?.geom
|
||||||
|
? parseFloat(location.value?.geom?.split(";")?.[0])
|
||||||
|
: undefined;
|
||||||
|
router.push({
|
||||||
|
name: RouteName.SEARCH,
|
||||||
|
query: {
|
||||||
|
locationName: location.value?.locality ?? location.value?.region,
|
||||||
|
lat,
|
||||||
|
lon,
|
||||||
|
search: search.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
>{{ t("Create an account") }}</o-button
|
>{{ t("Create an account") }}</o-button
|
||||||
>
|
>
|
||||||
<!-- We don't invite to find other instances yet -->
|
<!-- We don't invite to find other instances yet -->
|
||||||
<!-- <o-button v-else type="is-link" tag="a" href="https://joinmastodon.org">{{ t('Find an instance') }}</o-button> -->
|
<!-- <o-button v-else variant="link" tag="a" href="https://joinmastodon.org">{{ t('Find an instance') }}</o-button> -->
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: RouteName.ABOUT }"
|
:to="{ name: RouteName.ABOUT }"
|
||||||
class="py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-violet-title focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
class="py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-violet-title focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
||||||
@ -41,7 +41,12 @@ import { IConfig } from "@/types/config.model";
|
|||||||
import RouteName from "@/router/name";
|
import RouteName from "@/router/name";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
defineProps<{ config: IConfig }>();
|
defineProps<{
|
||||||
|
config: Pick<
|
||||||
|
IConfig,
|
||||||
|
"name" | "description" | "slogan" | "registrationsOpen"
|
||||||
|
>;
|
||||||
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n({ useScope: "global" });
|
const { t } = useI18n({ useScope: "global" });
|
||||||
</script>
|
</script>
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class="relative w-full snap-x snap-always snap-mandatory overflow-x-auto flex pb-6 gap-x-5 gap-y-8"
|
class="relative w-full snap-x snap-always snap-mandatory overflow-x-auto flex pb-6 gap-x-5 gap-y-8 p-1"
|
||||||
ref="scrollContainer"
|
ref="scrollContainer"
|
||||||
@scroll="scrollHandler"
|
@scroll="scrollHandler"
|
||||||
>
|
>
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
<more-content
|
<more-content
|
||||||
v-if="userLocationName && userLocation?.lat && userLocation?.lon"
|
v-if="userLocationName && userLocation?.lat && userLocation?.lon"
|
||||||
:to="{
|
:to="{
|
||||||
name: 'SEARCH',
|
name: RouteName.SEARCH,
|
||||||
query: {
|
query: {
|
||||||
locationName: userLocationName,
|
locationName: userLocationName,
|
||||||
lat: userLocation.lat?.toString(),
|
lat: userLocation.lat?.toString(),
|
||||||
@ -63,6 +63,8 @@ import { Paginate } from "@/types/paginate";
|
|||||||
import SkeletonEventResult from "../Event/SkeletonEventResult.vue";
|
import SkeletonEventResult from "../Event/SkeletonEventResult.vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { coordsToGeoHash } from "@/utils/location";
|
import { coordsToGeoHash } from "@/utils/location";
|
||||||
|
import { roundToNearestMinute } from "@/utils/datetime";
|
||||||
|
import RouteName from "@/router/name";
|
||||||
|
|
||||||
const props = defineProps<{ userLocation: LocationType }>();
|
const props = defineProps<{ userLocation: LocationType }>();
|
||||||
const emit = defineEmits(["doGeoLoc"]);
|
const emit = defineEmits(["doGeoLoc"]);
|
||||||
@ -77,17 +79,27 @@ const userLocationName = computed(() => {
|
|||||||
});
|
});
|
||||||
const suggestGeoloc = computed(() => props.userLocation?.isIPLocation);
|
const suggestGeoloc = computed(() => props.userLocation?.isIPLocation);
|
||||||
|
|
||||||
|
const geoHash = computed(() =>
|
||||||
|
coordsToGeoHash(props.userLocation.lat, props.userLocation.lon)
|
||||||
|
);
|
||||||
|
|
||||||
const { result: eventsResult, loading: loadingEvents } = useQuery<{
|
const { result: eventsResult, loading: loadingEvents } = useQuery<{
|
||||||
searchEvents: Paginate<IEvent>;
|
searchEvents: Paginate<IEvent>;
|
||||||
}>(SEARCH_EVENTS, () => ({
|
}>(
|
||||||
location: coordsToGeoHash(props.userLocation.lat, props.userLocation.lon),
|
SEARCH_EVENTS,
|
||||||
beginsOn: new Date(),
|
() => ({
|
||||||
|
location: geoHash.value,
|
||||||
|
beginsOn: roundToNearestMinute(new Date()),
|
||||||
endsOn: undefined,
|
endsOn: undefined,
|
||||||
radius: 25,
|
radius: 25,
|
||||||
eventPage: 1,
|
eventPage: 1,
|
||||||
limit: EVENT_PAGE_LIMIT,
|
limit: EVENT_PAGE_LIMIT,
|
||||||
type: "IN_PERSON",
|
type: "IN_PERSON",
|
||||||
}));
|
}),
|
||||||
|
() => ({
|
||||||
|
enabled: geoHash.value !== undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const events = computed(
|
const events = computed(
|
||||||
() => eventsResult.value?.searchEvents ?? { elements: [], total: 0 }
|
() => eventsResult.value?.searchEvents ?? { elements: [], total: 0 }
|
||||||
|
@ -18,12 +18,12 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<!-- <skeleton-group-result
|
<skeleton-group-result
|
||||||
v-for="i in [...Array(6).keys()]"
|
v-for="i in [...Array(6).keys()]"
|
||||||
class="scroll-ml-6 snap-center shrink-0 w-[18rem] my-4"
|
class="scroll-ml-6 snap-center shrink-0 w-[18rem] my-4"
|
||||||
:key="i"
|
:key="i"
|
||||||
v-show="loadingGroups"
|
v-show="loadingGroups"
|
||||||
/> -->
|
/>
|
||||||
<group-card
|
<group-card
|
||||||
v-for="group in selectedGroups"
|
v-for="group in selectedGroups"
|
||||||
:key="group.id"
|
:key="group.id"
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<more-content
|
<more-content
|
||||||
v-if="userLocationName"
|
v-if="userLocationName"
|
||||||
:to="{
|
:to="{
|
||||||
name: 'SEARCH',
|
name: RouteName.SEARCH,
|
||||||
query: {
|
query: {
|
||||||
locationName: userLocationName,
|
locationName: userLocationName,
|
||||||
lat: userLocation.lat?.toString(),
|
lat: userLocation.lat?.toString(),
|
||||||
@ -59,9 +59,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// import SkeletonGroupResult from "../../components/result/SkeletonGroupResult.vue";
|
import SkeletonGroupResult from "@/components/Group/SkeletonGroupResult.vue";
|
||||||
import sampleSize from "lodash/sampleSize";
|
import sampleSize from "lodash/sampleSize";
|
||||||
import { LocationType } from "../../types/user-location.model";
|
import { LocationType } from "@/types/user-location.model";
|
||||||
import MoreContent from "./MoreContent.vue";
|
import MoreContent from "./MoreContent.vue";
|
||||||
import CloseContent from "./CloseContent.vue";
|
import CloseContent from "./CloseContent.vue";
|
||||||
import { IGroup } from "@/types/actor";
|
import { IGroup } from "@/types/actor";
|
||||||
@ -72,6 +72,7 @@ import { computed } from "vue";
|
|||||||
import GroupCard from "@/components/Group/GroupCard.vue";
|
import GroupCard from "@/components/Group/GroupCard.vue";
|
||||||
import { coordsToGeoHash } from "@/utils/location";
|
import { coordsToGeoHash } from "@/utils/location";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import RouteName from "@/router/name";
|
||||||
|
|
||||||
const props = defineProps<{ userLocation: LocationType }>();
|
const props = defineProps<{ userLocation: LocationType }>();
|
||||||
const emit = defineEmits(["doGeoLoc"]);
|
const emit = defineEmits(["doGeoLoc"]);
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
/>
|
/>
|
||||||
<more-content
|
<more-content
|
||||||
:to="{
|
:to="{
|
||||||
name: 'SEARCH',
|
name: RouteName.SEARCH,
|
||||||
query: {
|
query: {
|
||||||
contentType: 'EVENTS',
|
contentType: 'EVENTS',
|
||||||
},
|
},
|
||||||
@ -57,6 +57,7 @@ import SkeletonEventResult from "../Event/SkeletonEventResult.vue";
|
|||||||
import { EventSortField, SortDirection } from "@/types/enums";
|
import { EventSortField, SortDirection } from "@/types/enums";
|
||||||
import { FETCH_EVENTS } from "@/graphql/event";
|
import { FETCH_EVENTS } from "@/graphql/event";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import RouteName from "@/router/name";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
instanceName: string;
|
instanceName: string;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<close-content
|
<close-content
|
||||||
|
class="container mx-auto px-2"
|
||||||
:suggest-geoloc="false"
|
:suggest-geoloc="false"
|
||||||
v-show="loadingEvents || events.length > 0"
|
v-show="loadingEvents || (events?.elements && events?.elements.length > 0)"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
{{ $t("Online upcoming events") }}
|
{{ $t("Online upcoming events") }}
|
||||||
@ -15,7 +16,7 @@
|
|||||||
/>
|
/>
|
||||||
<event-card
|
<event-card
|
||||||
class="scroll-ml-6 snap-center shrink-0 first:pl-8 last:pr-8 w-[18rem]"
|
class="scroll-ml-6 snap-center shrink-0 first:pl-8 last:pr-8 w-[18rem]"
|
||||||
v-for="event in events"
|
v-for="event in events?.elements"
|
||||||
:key="event.id"
|
:key="event.id"
|
||||||
:event="event"
|
:event="event"
|
||||||
view-mode="column"
|
view-mode="column"
|
||||||
@ -24,7 +25,7 @@
|
|||||||
/>
|
/>
|
||||||
<more-content
|
<more-content
|
||||||
:to="{
|
:to="{
|
||||||
name: 'SEARCH',
|
name: RouteName.SEARCH,
|
||||||
query: {
|
query: {
|
||||||
contentType: 'EVENTS',
|
contentType: 'EVENTS',
|
||||||
isOnline: 'true',
|
isOnline: 'true',
|
||||||
@ -50,25 +51,27 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import SkeletonEventResult from "../result/SkeletonEventResult.vue";
|
import SkeletonEventResult from "@/components/Event/SkeletonEventResult.vue";
|
||||||
import MoreContent from "./MoreContent.vue";
|
import MoreContent from "./MoreContent.vue";
|
||||||
import CloseContent from "./CloseContent.vue";
|
import CloseContent from "./CloseContent.vue";
|
||||||
import { SEARCH_EVENTS } from "@/graphql/search";
|
import { SEARCH_EVENTS } from "@/graphql/search";
|
||||||
import EventCard from "../../components/Event/EventCard.vue";
|
import EventCard from "@/components/Event/EventCard.vue";
|
||||||
import { useQuery } from "@vue/apollo-composable";
|
import { useQuery } from "@vue/apollo-composable";
|
||||||
|
import RouteName from "@/router/name";
|
||||||
|
import { Paginate } from "@/types/paginate";
|
||||||
|
import { IEvent } from "@/types/event.model";
|
||||||
|
|
||||||
const EVENT_PAGE_LIMIT = 12;
|
const EVENT_PAGE_LIMIT = 12;
|
||||||
|
|
||||||
const { result: searchEventResult, loading: loadingEvents } = useQuery(
|
const { result: searchEventResult, loading: loadingEvents } = useQuery<{
|
||||||
SEARCH_EVENTS,
|
searchEvents: Paginate<IEvent>;
|
||||||
() => ({
|
}>(SEARCH_EVENTS, () => ({
|
||||||
beginsOn: new Date(),
|
beginsOn: new Date(),
|
||||||
endsOn: undefined,
|
endsOn: undefined,
|
||||||
eventPage: 1,
|
eventPage: 1,
|
||||||
limit: EVENT_PAGE_LIMIT,
|
limit: EVENT_PAGE_LIMIT,
|
||||||
type: "ONLINE",
|
type: "ONLINE",
|
||||||
})
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
const events = computed(() => searchEventResult.value.searchEvents);
|
const events = computed(() => searchEventResult.value?.searchEvents);
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg
|
<svg
|
||||||
class="bg-white dark:bg-gray-900 dark:fill-white"
|
class="bg-white dark:bg-zinc-900 dark:fill-white"
|
||||||
:class="{ 'bg-gray-900': invert }"
|
:class="{ 'bg-gray-900': invert }"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 248.16 46.78"
|
viewBox="0 0 248.16 46.78"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-gray-900">
|
<nav class="bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-zinc-900">
|
||||||
<div class="container mx-auto flex flex-wrap items-center mx-auto gap-4">
|
<div class="container mx-auto flex flex-wrap items-center mx-auto gap-4">
|
||||||
<router-link :to="{ name: RouteName.HOME }" class="flex items-center">
|
<router-link :to="{ name: RouteName.HOME }" class="flex items-center">
|
||||||
<MobilizonLogo class="w-40" />
|
<MobilizonLogo class="w-40" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="flex items-center md:order-2 ml-auto" v-if="currentActor?.id">
|
<div class="flex items-center md:order-2 ml-auto" v-if="currentActor?.id">
|
||||||
<o-dropdown>
|
<o-dropdown position="bottom-left">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -14,33 +14,80 @@
|
|||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
<span class="sr-only">{{ t("Open user menu") }}</span>
|
<span class="sr-only">{{ t("Open user menu") }}</span>
|
||||||
<figure class="" v-if="currentActor?.avatar">
|
<figure class="h-8 w-8" v-if="currentActor?.avatar">
|
||||||
<img
|
<img
|
||||||
class="rounded-full"
|
class="rounded-full w-full h-full object-cover"
|
||||||
alt=""
|
alt=""
|
||||||
:src="currentActor?.avatar.url"
|
:src="currentActor?.avatar.url"
|
||||||
width="32"
|
width="32"
|
||||||
height="32"
|
height="32"
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
<AccountCircle :size="32" />
|
<AccountCircle v-else :size="32" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Dropdown menu -->
|
<!-- Dropdown menu -->
|
||||||
<div
|
<div
|
||||||
class="z-50 mt-4 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700 dark:divide-gray-600"
|
class="z-50 text-base list-none bg-white rounded divide-y divide-gray-100 dark:bg-zinc-700 dark:divide-gray-600 max-w-xs"
|
||||||
position="bottom-left"
|
position="bottom-left"
|
||||||
>
|
>
|
||||||
<o-dropdown-item aria-role="listitem">
|
<o-dropdown-item aria-role="listitem">
|
||||||
<div class="">
|
<div class="px-4">
|
||||||
<span class="block text-sm text-gray-900 dark:text-white">{{
|
<span class="block text-sm text-zinc-900 dark:text-white">{{
|
||||||
displayName(currentActor)
|
displayName(currentActor)
|
||||||
}}</span>
|
}}</span>
|
||||||
<span
|
<span
|
||||||
class="block text-sm font-medium text-gray-500 truncate dark:text-gray-400"
|
class="block text-sm font-medium text-zinc-500 truncate dark:text-zinc-400"
|
||||||
>{{ currentUser?.role }}</span
|
v-if="currentUser?.role === ICurrentUserRole.ADMINISTRATOR"
|
||||||
|
>{{ t("Administrator") }}</span
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="block text-sm font-medium text-zinc-500 truncate dark:text-zinc-400"
|
||||||
|
v-if="currentUser?.role === ICurrentUserRole.MODERATOR"
|
||||||
|
>{{ t("Moderator") }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</o-dropdown-item>
|
||||||
|
<o-dropdown-item
|
||||||
|
v-for="identity in identities"
|
||||||
|
:active="identity.id === currentActor.id"
|
||||||
|
:key="identity.id"
|
||||||
|
tabindex="0"
|
||||||
|
@click="
|
||||||
|
setIdentity({
|
||||||
|
preferredUsername: identity.preferredUsername,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
@keyup.enter="
|
||||||
|
setIdentity({
|
||||||
|
preferredUsername: identity.preferredUsername,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="flex gap-1 items-center">
|
||||||
|
<div class="flex-none">
|
||||||
|
<figure class="" v-if="identity.avatar">
|
||||||
|
<img
|
||||||
|
class="rounded-full h-8 w-8"
|
||||||
|
loading="lazy"
|
||||||
|
:src="identity.avatar.url"
|
||||||
|
alt=""
|
||||||
|
height="32"
|
||||||
|
width="32"
|
||||||
|
/>
|
||||||
|
</figure>
|
||||||
|
<AccountCircle v-else :size="32" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="text-base text-zinc-700 dark:text-zinc-100 flex flex-col flex-auto overflow-hidden items-start"
|
||||||
|
>
|
||||||
|
<p class="truncate">{{ displayName(identity) }}</p>
|
||||||
|
<p class="truncate text-sm" v-if="identity.name">
|
||||||
|
@{{ identity.preferredUsername }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</o-dropdown-item>
|
</o-dropdown-item>
|
||||||
<o-dropdown-item
|
<o-dropdown-item
|
||||||
@ -49,7 +96,7 @@
|
|||||||
:to="{ name: RouteName.SETTINGS }"
|
:to="{ name: RouteName.SETTINGS }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="block py-2 px-4 text-sm text-gray-700 dark:text-gray-200 dark:hover:text-white"
|
class="block py-2 px-4 text-sm text-zinc-700 dark:text-zinc-200 dark:hover:text-white"
|
||||||
>{{ t("My account") }}</span
|
>{{ t("My account") }}</span
|
||||||
>
|
>
|
||||||
</o-dropdown-item>
|
</o-dropdown-item>
|
||||||
@ -60,7 +107,7 @@
|
|||||||
:to="{ name: RouteName.ADMIN_DASHBOARD }"
|
:to="{ name: RouteName.ADMIN_DASHBOARD }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="block py-2 px-4 text-sm text-gray-700 dark:text-gray-200 dark:hover:text-white"
|
class="block py-2 px-4 text-sm text-zinc-700 dark:text-zinc-200 dark:hover:text-white"
|
||||||
>{{ t("Administration") }}</span
|
>{{ t("Administration") }}</span
|
||||||
>
|
>
|
||||||
</o-dropdown-item>
|
</o-dropdown-item>
|
||||||
@ -70,7 +117,7 @@
|
|||||||
@keyup.enter="logout"
|
@keyup.enter="logout"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="block py-2 px-4 text-sm text-gray-700 dark:text-gray-200 dark:hover:text-white"
|
class="block py-2 px-4 text-sm text-zinc-700 dark:text-zinc-200 dark:hover:text-white"
|
||||||
>{{ t("Log out") }}</span
|
>{{ t("Log out") }}</span
|
||||||
>
|
>
|
||||||
</o-dropdown-item>
|
</o-dropdown-item>
|
||||||
@ -80,7 +127,7 @@
|
|||||||
<button
|
<button
|
||||||
@click="showMobileMenu = !showMobileMenu"
|
@click="showMobileMenu = !showMobileMenu"
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex items-center p-2 ml-1 text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
|
class="inline-flex items-center p-2 ml-1 text-sm text-zinc-500 rounded-lg md:hidden hover:bg-zinc-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:focus:ring-gray-600"
|
||||||
aria-controls="mobile-menu-2"
|
aria-controls="mobile-menu-2"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
@ -105,33 +152,33 @@
|
|||||||
:class="{ hidden: !showMobileMenu }"
|
:class="{ hidden: !showMobileMenu }"
|
||||||
>
|
>
|
||||||
<ul
|
<ul
|
||||||
class="flex flex-col md:flex-row md:space-x-8 mt-2 md:mt-0 md:text-sm md:font-medium"
|
class="flex flex-col md:flex-row md:space-x-8 mt-2 md:mt-0 md:font-lightbold"
|
||||||
>
|
>
|
||||||
<li v-if="currentActor?.id">
|
<li v-if="currentActor?.id">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: RouteName.MY_EVENTS }"
|
:to="{ name: RouteName.MY_EVENTS }"
|
||||||
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
class="block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||||
>{{ t("My events") }}</router-link
|
>{{ t("My events") }}</router-link
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="currentActor?.id">
|
<li v-if="currentActor?.id">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: RouteName.MY_GROUPS }"
|
:to="{ name: RouteName.MY_GROUPS }"
|
||||||
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
class="block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||||
>{{ t("My groups") }}</router-link
|
>{{ t("My groups") }}</router-link
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!currentActor?.id">
|
<li v-if="!currentActor?.id">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: RouteName.LOGIN }"
|
:to="{ name: RouteName.LOGIN }"
|
||||||
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
class="block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||||
>{{ t("Login") }}</router-link
|
>{{ t("Login") }}</router-link
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!currentActor?.id">
|
<li v-if="!currentActor?.id">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: RouteName.REGISTER }"
|
:to="{ name: RouteName.REGISTER }"
|
||||||
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
class="block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
|
||||||
>{{ t("Register") }}</router-link
|
>{{ t("Register") }}</router-link
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
@ -327,6 +374,9 @@ import {
|
|||||||
useCurrentActorClient,
|
useCurrentActorClient,
|
||||||
useCurrentUserIdentities,
|
useCurrentUserIdentities,
|
||||||
} from "@/composition/apollo/actor";
|
} from "@/composition/apollo/actor";
|
||||||
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
|
import { UPDATE_DEFAULT_ACTOR } from "@/graphql/actor";
|
||||||
|
import { changeIdentity } from "@/utils/identity";
|
||||||
// import { useRestrictions } from "@/composition/apollo/config";
|
// import { useRestrictions } from "@/composition/apollo/config";
|
||||||
|
|
||||||
const { currentUser } = useCurrentUserClient();
|
const { currentUser } = useCurrentUserClient();
|
||||||
@ -400,11 +450,17 @@ watch(identities, () => {
|
|||||||
// await router.push({ name: RouteName.HOME });
|
// await router.push({ name: RouteName.HOME });
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const { onDone, mutate: setIdentity } = useMutation(UPDATE_DEFAULT_ACTOR);
|
const { onDone, mutate: setIdentity } = useMutation<{
|
||||||
|
changeDefaultActor: { id: string; defaultActor: { id: string } };
|
||||||
|
}>(UPDATE_DEFAULT_ACTOR);
|
||||||
|
|
||||||
// onDone(() => {
|
onDone(({ data }) => {
|
||||||
// changeIdentity(identity);
|
const identity = identities.value?.find(
|
||||||
// });
|
({ id }) => id === data?.changeDefaultActor?.defaultActor?.id
|
||||||
|
);
|
||||||
|
if (!identity) return;
|
||||||
|
changeIdentity(identity);
|
||||||
|
});
|
||||||
|
|
||||||
// const hideCreateEventsButton = computed((): boolean => {
|
// const hideCreateEventsButton = computed((): boolean => {
|
||||||
// return !!restrictions.value?.onlyGroupsCanCreateEvents;
|
// return !!restrictions.value?.onlyGroupsCanCreateEvents;
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="container mx-auto">
|
<section class="container mx-auto">
|
||||||
<h1 class="title" v-if="loading">
|
<h1 class="title" v-if="loading">
|
||||||
{{ $t("Your participation request is being validated") }}
|
{{ t("Your participation request is being validated") }}
|
||||||
</h1>
|
</h1>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-if="failed && participation === undefined">
|
<div v-if="failed && participation === undefined">
|
||||||
<o-notification
|
<o-notification
|
||||||
:title="$t('Error while validating participation request')"
|
:title="t('Error while validating participation request')"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
$t(
|
t(
|
||||||
"Either the participation request has already been validated, either the validation token is incorrect."
|
"Either the participation request has already been validated, either the validation token is incorrect."
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@ -18,27 +18,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
{{ $t("Your participation request has been validated") }}
|
{{ t("Your participation request has been validated") }}
|
||||||
</h1>
|
</h1>
|
||||||
<p
|
<p
|
||||||
class="prose dark:prose-invert"
|
class="prose dark:prose-invert"
|
||||||
v-if="participation?.event.joinOptions == EventJoinOptions.RESTRICTED"
|
v-if="participation?.event.joinOptions == EventJoinOptions.RESTRICTED"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
$t("Your participation still has to be approved by the organisers.")
|
t("Your participation still has to be approved by the organisers.")
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
<div v-if="failed">
|
<div v-if="failed">
|
||||||
<o-notification
|
<o-notification
|
||||||
:title="
|
:title="
|
||||||
$t(
|
t('Error while updating participation status inside this browser')
|
||||||
'Error while updating participation status inside this browser'
|
|
||||||
)
|
|
||||||
"
|
"
|
||||||
variant="warning"
|
variant="warning"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
$t(
|
t(
|
||||||
"We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue."
|
"We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue."
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@ -46,15 +44,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="columns has-text-centered">
|
<div class="columns has-text-centered">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<router-link
|
<o-button
|
||||||
native-type="button"
|
tag="router-link"
|
||||||
tag="a"
|
variant="primary"
|
||||||
class="button is-primary is-large"
|
size="large"
|
||||||
:to="{
|
:to="{
|
||||||
name: RouteName.EVENT,
|
name: RouteName.EVENT,
|
||||||
params: { uuid: participation?.event.uuid },
|
params: { uuid: participation?.event.uuid },
|
||||||
}"
|
}"
|
||||||
>{{ $t("Go to the event page") }}</router-link
|
>{{ t("Go to the event page") }}</o-button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,10 +26,7 @@
|
|||||||
<template #popper>
|
<template #popper>
|
||||||
{{ t("Click for more information") }}
|
{{ t("Click for more information") }}
|
||||||
</template>
|
</template>
|
||||||
<span
|
<span @click="isAnonymousParticipationModalOpen = true">
|
||||||
class="is-clickable"
|
|
||||||
@click="isAnonymousParticipationModalOpen = true"
|
|
||||||
>
|
|
||||||
<InformationOutline :size="16" />
|
<InformationOutline :size="16" />
|
||||||
</span>
|
</span>
|
||||||
</VTooltip>
|
</VTooltip>
|
||||||
@ -102,7 +99,8 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="buttons" v-if="isSecureContext()">
|
<div class="buttons" v-if="isSecureContext()">
|
||||||
<o-button
|
<o-button
|
||||||
type="is-danger is-outlined"
|
variant="danger"
|
||||||
|
outlined
|
||||||
@click="clearEventParticipationData"
|
@click="clearEventParticipationData"
|
||||||
>
|
>
|
||||||
{{ t("Clear participation data for this event") }}
|
{{ t("Clear participation data for this event") }}
|
||||||
@ -197,7 +195,7 @@ const isEventNotAlreadyPassed = computed((): boolean => {
|
|||||||
return new Date(endDate.value) > new Date();
|
return new Date(endDate.value) > new Date();
|
||||||
});
|
});
|
||||||
|
|
||||||
const endDate = computed((): Date => {
|
const endDate = computed((): string => {
|
||||||
return props.event.endsOn !== null &&
|
return props.event.endsOn !== null &&
|
||||||
props.event.endsOn > props.event.beginsOn
|
props.event.endsOn > props.event.beginsOn
|
||||||
? props.event.endsOn
|
? props.event.endsOn
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
}}
|
}}
|
||||||
</small>
|
</small>
|
||||||
<o-tooltip
|
<o-tooltip
|
||||||
type="is-dark"
|
variant="dark"
|
||||||
:label="
|
:label="
|
||||||
$t(
|
$t(
|
||||||
'Mobilizon is a federated network. You can interact with this event from a different server.'
|
'Mobilizon is a federated network. You can interact with this event from a different server.'
|
||||||
@ -90,7 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<o-button tag="a" type="is-text" @click="router.go(-1)">{{
|
<o-button tag="a" variant="text" @click="router.go(-1)">{{
|
||||||
$t("Back to previous page")
|
$t("Back to previous page")
|
||||||
}}</o-button>
|
}}</o-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
</o-upload>
|
</o-upload>
|
||||||
</o-field>
|
</o-field>
|
||||||
<o-button
|
<o-button
|
||||||
type="is-text"
|
variant="text"
|
||||||
v-if="imageSrc"
|
v-if="imageSrc"
|
||||||
@click="removeOrClearPicture"
|
@click="removeOrClearPicture"
|
||||||
@keyup.enter="removeOrClearPicture"
|
@keyup.enter="removeOrClearPicture"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="" v-if="report">
|
<div class="dark:bg-zinc-700 p-2 rounded" v-if="report">
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<figure class="" v-if="report.reported.avatar">
|
<figure class="" v-if="report.reported.avatar">
|
||||||
<img
|
<img
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="preview">
|
<div class="preview text-mbz-purple dark:text-mbz-purple-300">
|
||||||
<Folder :size="48" />
|
<Folder :size="48" />
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
@ -39,7 +39,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import Draggable, { ChangeEvent } from "@xiaoshuapp/draggable";
|
// import Draggable, { ChangeEvent } from "@xiaoshuapp/draggable";
|
||||||
// import { SnackbarProgrammatic as Snackbar } from "buefy";
|
// import { SnackbarProgrammatic as Snackbar } from "buefy";
|
||||||
import { IResource } from "@/types/resource";
|
import { IResource } from "@/types/resource";
|
||||||
import RouteName from "@/router/name";
|
import RouteName from "@/router/name";
|
||||||
@ -110,8 +110,8 @@ onMovedResource(({ data }) => {
|
|||||||
onMovedResourceError((e) => {
|
onMovedResourceError((e) => {
|
||||||
// Snackbar.open({
|
// Snackbar.open({
|
||||||
// message: e.message,
|
// message: e.message,
|
||||||
// type: "is-danger",
|
// variant: "danger",
|
||||||
// position: "is-bottom",
|
// position: "bottom",
|
||||||
// });
|
// });
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-1 items-center w-full" dir="auto">
|
<div class="flex flex-1 items-center w-full" dir="auto">
|
||||||
<a :href="resource.resourceUrl" target="_blank">
|
<a :href="resource.resourceUrl" target="_blank">
|
||||||
<div class="preview">
|
<div class="preview text-mbz-purple dark:text-mbz-purple-300">
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
resource.type &&
|
resource.type &&
|
||||||
@ -79,7 +79,7 @@ const emit = defineEmits<{
|
|||||||
(e: "delete", resourceID: string): void;
|
(e: "delete", resourceID: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const list = ref([]);
|
// const list = ref([]);
|
||||||
|
|
||||||
const urlHostname = computed((): string | undefined => {
|
const urlHostname = computed((): string | undefined => {
|
||||||
if (props.resource?.resourceUrl) {
|
if (props.resource?.resourceUrl) {
|
||||||
|
@ -69,7 +69,7 @@
|
|||||||
/>
|
/>
|
||||||
</article>
|
</article>
|
||||||
<div class="flex gap-2 mt-2">
|
<div class="flex gap-2 mt-2">
|
||||||
<o-button type="is-text" @click="emit('close-move-modal')">{{
|
<o-button variant="text" @click="emit('close-move-modal')">{{
|
||||||
$t("Cancel")
|
$t("Cancel")
|
||||||
}}</o-button>
|
}}</o-button>
|
||||||
<o-button
|
<o-button
|
||||||
|
@ -56,8 +56,8 @@ const updateSetting = async (
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// Snackbar.open({
|
// Snackbar.open({
|
||||||
// message: e.message,
|
// message: e.message,
|
||||||
// type: "is-danger",
|
// variant: "danger",
|
||||||
// position: "is-bottom",
|
// position: "bottom",
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<li class="setting-menu-item" :class="{ active: isActive }">
|
<li
|
||||||
|
class="setting-menu-item"
|
||||||
|
:class="{
|
||||||
|
'cursor-pointer bg-mbz-yellow-alt-500 dark:bg-mbz-purple-500': isActive,
|
||||||
|
'bg-mbz-yellow-alt-100 hover:bg-mbz-yellow-alt-200 dark:bg-mbz-purple-300 dark:hover:bg-mbz-purple-400 dark:text-white':
|
||||||
|
!isActive,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<router-link v-if="to" :to="to">
|
<router-link v-if="to" :to="to">
|
||||||
<span>{{ title }}</span>
|
<span>{{ title }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
@ -31,7 +38,7 @@ const isActive = computed((): boolean => {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
li.setting-menu-item {
|
li.setting-menu-item {
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
background-color: #fff1de;
|
// background-color: #fff1de;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@ -47,7 +54,7 @@ li.setting-menu-item {
|
|||||||
&:hover,
|
&:hover,
|
||||||
&.active {
|
&.active {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: lighten(#fea72b, 10%);
|
// background-color: lighten(#fea72b, 10%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<li class="bg-yellow-1 text-violet-2 text-xl">
|
<li
|
||||||
|
class="bg-mbz-yellow-alt-300 text-violet-2 dark:bg-mbz-purple-500 dark:text-zinc-100 text-xl"
|
||||||
|
>
|
||||||
<router-link
|
<router-link
|
||||||
class="cursor-pointer my-2 mx-0 py-2 px-3 font-medium block no-underline"
|
class="cursor-pointer my-2 mx-0 py-2 px-3 font-medium block no-underline"
|
||||||
v-if="to"
|
v-if="to"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<aside>
|
<aside class="mb-6">
|
||||||
<ul>
|
<ul>
|
||||||
<SettingMenuSection
|
<SettingMenuSection
|
||||||
:title="t('Account')"
|
:title="t('Account')"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="icon has-text-primary is-large">
|
<span class="text-black dark:text-white dark:fill-white">
|
||||||
<svg
|
<svg
|
||||||
version="1.1"
|
version="1.1"
|
||||||
viewBox="0 0 65.131 65.131"
|
viewBox="0 0 65.131 65.131"
|
||||||
@ -12,7 +12,7 @@
|
|||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="m23.631 51.953c-2.348-1.5418-6.9154-5.1737-7.0535-5.6088-0.06717-0.21164 0.45125-0.99318 3.3654-5.0734 2.269-3.177 3.7767-5.3581 3.7767-5.4637 0-0.03748-1.6061-0.60338-3.5691-1.2576-6.1342-2.0442-8.3916-2.9087-8.5288-3.2663-0.03264-0.08506 0.09511-0.68598 0.28388-1.3354 0.643-2.212 2.7038-8.4123 2.7959-8.4123 0.05052 0 2.6821 0.85982 5.848 1.9107 3.1659 1.0509 5.897 1.9222 6.0692 1.9362 0.3089 0.02514 0.31402 0.01925 0.38295-0.44107 0.09851-0.65784 0.26289-5.0029 0.2633-6.9599 1.87e-4 -0.90267 0.02801-2.5298 0.06184-3.6158l0.0615-1.9746h10.392l0.06492 4.4556c0.06287 4.3148 0.18835 7.8236 0.29865 8.3513 0.0295 0.14113 0.11236 0.2566 0.18412 0.2566 0.07176 0 1.6955-0.50861 3.6084-1.1303 4.5213-1.4693 6.2537-2.0038 7.3969-2.2822 0.87349-0.21269 0.94061-0.21704 1.0505-0.06806 0.45169 0.61222 3.3677 9.2365 3.1792 9.4025-0.33681 0.29628-2.492 1.1048-6.9823 2.6194-5.3005 1.7879-5.1321 1.7279-5.1321 1.8283 0 0.13754 0.95042 1.522 3.5468 5.1666 1.3162 1.8475 2.6802 3.7905 3.0311 4.3176l0.63804 0.95842-0.27216 0.28519c-1.1112 1.1644-7.3886 5.8693-7.8309 5.8693-0.22379 0-1.2647-1.2321-2.9284-3.4663-0.90374-1.2137-2.264-3.0402-3.0228-4.059-0.75878-1.0188-1.529-2.0203-1.7116-2.2256l-0.33201-0.37324-0.32674 0.37324c-0.43918 0.50169-2.226 2.867-3.8064 5.0388-2.1662 2.9767-3.6326 4.8055-3.8532 4.8055-0.05161 0-0.4788-0.25278-0.94931-0.56173z"
|
d="m23.631 51.953c-2.348-1.5418-6.9154-5.1737-7.0535-5.6088-0.06717-0.21164 0.45125-0.99318 3.3654-5.0734 2.269-3.177 3.7767-5.3581 3.7767-5.4637 0-0.03748-1.6061-0.60338-3.5691-1.2576-6.1342-2.0442-8.3916-2.9087-8.5288-3.2663-0.03264-0.08506 0.09511-0.68598 0.28388-1.3354 0.643-2.212 2.7038-8.4123 2.7959-8.4123 0.05052 0 2.6821 0.85982 5.848 1.9107 3.1659 1.0509 5.897 1.9222 6.0692 1.9362 0.3089 0.02514 0.31402 0.01925 0.38295-0.44107 0.09851-0.65784 0.26289-5.0029 0.2633-6.9599 1.87e-4 -0.90267 0.02801-2.5298 0.06184-3.6158l0.0615-1.9746h10.392l0.06492 4.4556c0.06287 4.3148 0.18835 7.8236 0.29865 8.3513 0.0295 0.14113 0.11236 0.2566 0.18412 0.2566 0.07176 0 1.6955-0.50861 3.6084-1.1303 4.5213-1.4693 6.2537-2.0038 7.3969-2.2822 0.87349-0.21269 0.94061-0.21704 1.0505-0.06806 0.45169 0.61222 3.3677 9.2365 3.1792 9.4025-0.33681 0.29628-2.492 1.1048-6.9823 2.6194-5.3005 1.7879-5.1321 1.7279-5.1321 1.8283 0 0.13754 0.95042 1.522 3.5468 5.1666 1.3162 1.8475 2.6802 3.7905 3.0311 4.3176l0.63804 0.95842-0.27216 0.28519c-1.1112 1.1644-7.3886 5.8693-7.8309 5.8693-0.22379 0-1.2647-1.2321-2.9284-3.4663-0.90374-1.2137-2.264-3.0402-3.0228-4.059-0.75878-1.0188-1.529-2.0203-1.7116-2.2256l-0.33201-0.37324-0.32674 0.37324c-0.43918 0.50169-2.226 2.867-3.8064 5.0388-2.1662 2.9767-3.6326 4.8055-3.8532 4.8055-0.05161 0-0.4788-0.25278-0.94931-0.56173z"
|
||||||
fill="#fff"
|
fill="transparent"
|
||||||
stroke-width=".093311"
|
stroke-width=".093311"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="icon has-text-primary is-large">
|
<span class="text-primary dark:text-white">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976">
|
||||||
<title>Mastodon logo</title>
|
<title>Mastodon logo</title>
|
||||||
<path
|
<path
|
||||||
|
@ -5,13 +5,13 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="flex">
|
<section class="flex">
|
||||||
<div class="">
|
<div class="w-full">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<o-field :label="inputLabel" label-for="url-text">
|
<o-field :label="inputLabel" label-for="url-text">
|
||||||
<o-input id="url-text" ref="URLInput" :modelValue="url" expanded />
|
<o-input id="url-text" ref="URLInput" :modelValue="url" expanded />
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<o-tooltip
|
<o-tooltip
|
||||||
:label="$t('URL copied to clipboard')"
|
:label="t('URL copied to clipboard')"
|
||||||
:active="showCopiedTooltip"
|
:active="showCopiedTooltip"
|
||||||
always
|
always
|
||||||
variant="success"
|
variant="success"
|
||||||
@ -23,7 +23,7 @@
|
|||||||
native-type="button"
|
native-type="button"
|
||||||
@click="copyURL"
|
@click="copyURL"
|
||||||
@keyup.enter="copyURL"
|
@keyup.enter="copyURL"
|
||||||
:title="$t('Copy URL to clipboard')"
|
:title="t('Copy URL to clipboard')"
|
||||||
/>
|
/>
|
||||||
</o-tooltip>
|
</o-tooltip>
|
||||||
</p>
|
</p>
|
||||||
@ -34,7 +34,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="nofollow noopener"
|
rel="nofollow noopener"
|
||||||
title="Twitter"
|
title="Twitter"
|
||||||
><Twitter :size="48"
|
><Twitter :size="48" class="dark:text-white"
|
||||||
/></a>
|
/></a>
|
||||||
<a
|
<a
|
||||||
:href="mastodonShare"
|
:href="mastodonShare"
|
||||||
@ -50,14 +50,14 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="nofollow noopener"
|
rel="nofollow noopener"
|
||||||
title="Facebook"
|
title="Facebook"
|
||||||
><Facebook :size="48"
|
><Facebook :size="48" class="dark:text-white"
|
||||||
/></a>
|
/></a>
|
||||||
<a
|
<a
|
||||||
:href="whatsAppShare"
|
:href="whatsAppShare"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="nofollow noopener"
|
rel="nofollow noopener"
|
||||||
title="WhatsApp"
|
title="WhatsApp"
|
||||||
><Whatsapp :size="48"
|
><Whatsapp :size="48" class="dark:text-white"
|
||||||
/></a>
|
/></a>
|
||||||
<a
|
<a
|
||||||
:href="telegramShare"
|
:href="telegramShare"
|
||||||
@ -73,7 +73,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="nofollow noopener"
|
rel="nofollow noopener"
|
||||||
title="LinkedIn"
|
title="LinkedIn"
|
||||||
><LinkedIn :size="48"
|
><LinkedIn :size="48" class="dark:text-white"
|
||||||
/></a>
|
/></a>
|
||||||
<a
|
<a
|
||||||
:href="diasporaShare"
|
:href="diasporaShare"
|
||||||
@ -90,7 +90,7 @@
|
|||||||
rel="nofollow noopener"
|
rel="nofollow noopener"
|
||||||
title="Email"
|
title="Email"
|
||||||
>
|
>
|
||||||
<Email :size="48" />
|
<Email :size="48" class="dark:text-white" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -118,6 +118,7 @@ import {
|
|||||||
twitterShareUrl,
|
twitterShareUrl,
|
||||||
whatsAppShareUrl,
|
whatsAppShareUrl,
|
||||||
} from "@/utils/share";
|
} from "@/utils/share";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@ -129,7 +130,9 @@ const props = withDefaults(
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const URLInput = ref<HTMLElement | null>(null);
|
const { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
|
const URLInput = ref<{ $refs: { input: HTMLInputElement } } | null>(null);
|
||||||
|
|
||||||
const showCopiedTooltip = ref(false);
|
const showCopiedTooltip = ref(false);
|
||||||
|
|
||||||
@ -159,7 +162,6 @@ const mastodonShare = computed((): string | undefined =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const copyURL = (): void => {
|
const copyURL = (): void => {
|
||||||
console.log("URLInput", URLInput.value);
|
|
||||||
URLInput.value?.$refs.input.select();
|
URLInput.value?.$refs.input.select();
|
||||||
document.execCommand("copy");
|
document.execCommand("copy");
|
||||||
showCopiedTooltip.value = true;
|
showCopiedTooltip.value = true;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="icon has-text-primary is-large">
|
<span class="text-primary dark:text-white dark:fill-white">
|
||||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<title>Telegram</title>
|
<title>Telegram</title>
|
||||||
<path
|
<path
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
class="rounded-md my-1 truncate text-sm text-violet-title px-2 py-1"
|
class="rounded-md my-1 truncate text-sm text-violet-title px-2 py-1"
|
||||||
:class="[typeClasses, capitalize]"
|
:class="[
|
||||||
|
typeClasses,
|
||||||
|
capitalize,
|
||||||
|
withHashTag ? `before:content-['#']` : '',
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
@ -11,10 +15,11 @@ import { computed } from "vue";
|
|||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
variant?: "info" | "danger" | "warning" | "light";
|
variant?: "info" | "danger" | "warning" | "light" | "primary";
|
||||||
capitalize: boolean;
|
capitalize?: boolean;
|
||||||
|
withHashTag?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{ variant: "light", capitalize: false }
|
{ variant: "light", capitalize: false, withHashTag: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
const typeClasses = computed(() => {
|
const typeClasses = computed(() => {
|
||||||
@ -23,7 +28,7 @@ const typeClasses = computed(() => {
|
|||||||
case "light":
|
case "light":
|
||||||
return "bg-purple-3 dark:text-violet-3";
|
return "bg-purple-3 dark:text-violet-3";
|
||||||
case "info":
|
case "info":
|
||||||
return "bg-mbz-info dark:text-white";
|
return "bg-mbz-info dark:text-black";
|
||||||
case "warning":
|
case "warning":
|
||||||
return "bg-yellow-1";
|
return "bg-yellow-1";
|
||||||
case "danger":
|
case "danger":
|
||||||
@ -33,9 +38,7 @@ const typeClasses = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
span.tag {
|
span.withHashTag::before {
|
||||||
&:not(.category)::before {
|
|
||||||
content: "#";
|
content: "#";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
:data-actor-id="currentActor && currentActor.id"
|
:data-actor-id="currentActor && currentActor.id"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="menubar bar-is-hidden"
|
class="mb-2 menubar bar-is-hidden"
|
||||||
v-if="isDescriptionMode"
|
v-if="isDescriptionMode"
|
||||||
:editor="editor"
|
:editor="editor"
|
||||||
>
|
>
|
||||||
@ -16,9 +16,9 @@
|
|||||||
:class="{ 'is-active': editor.isActive('bold') }"
|
:class="{ 'is-active': editor.isActive('bold') }"
|
||||||
@click="editor?.chain().focus().toggleBold().run()"
|
@click="editor?.chain().focus().toggleBold().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Bold')"
|
:title="t('Bold')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-bold" />
|
<FormatBold :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -26,9 +26,9 @@
|
|||||||
:class="{ 'is-active': editor.isActive('italic') }"
|
:class="{ 'is-active': editor.isActive('italic') }"
|
||||||
@click="editor?.chain().focus().toggleItalic().run()"
|
@click="editor?.chain().focus().toggleItalic().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Italic')"
|
:title="t('Italic')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-italic" />
|
<FormatItalic :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -36,9 +36,9 @@
|
|||||||
:class="{ 'is-active': editor.isActive('underline') }"
|
:class="{ 'is-active': editor.isActive('underline') }"
|
||||||
@click="editor?.chain().focus().toggleUnderline().run()"
|
@click="editor?.chain().focus().toggleUnderline().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Underline')"
|
:title="t('Underline')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-underline" />
|
<FormatUnderline :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -47,9 +47,9 @@
|
|||||||
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
|
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
|
||||||
@click="editor?.chain().focus().toggleHeading({ level: 1 }).run()"
|
@click="editor?.chain().focus().toggleHeading({ level: 1 }).run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Heading Level 1')"
|
:title="t('Heading Level 1')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-header-1" />
|
<FormatHeader1 :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -58,9 +58,9 @@
|
|||||||
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
|
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
|
||||||
@click="editor?.chain().focus().toggleHeading({ level: 2 }).run()"
|
@click="editor?.chain().focus().toggleHeading({ level: 2 }).run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Heading Level 2')"
|
:title="t('Heading Level 2')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-header-2" />
|
<FormatHeader2 :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -69,9 +69,9 @@
|
|||||||
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
|
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
|
||||||
@click="editor?.chain().focus().toggleHeading({ level: 3 }).run()"
|
@click="editor?.chain().focus().toggleHeading({ level: 3 }).run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Heading Level 3')"
|
:title="t('Heading Level 3')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-header-3" />
|
<FormatHeader3 :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -79,9 +79,9 @@
|
|||||||
@click="showLinkMenu()"
|
@click="showLinkMenu()"
|
||||||
:class="{ 'is-active': editor.isActive('link') }"
|
:class="{ 'is-active': editor.isActive('link') }"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Add link')"
|
:title="t('Add link')"
|
||||||
>
|
>
|
||||||
<o-icon icon="link" />
|
<LinkIcon :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -89,9 +89,9 @@
|
|||||||
class="menubar__button"
|
class="menubar__button"
|
||||||
@click="editor?.chain().focus().unsetLink().run()"
|
@click="editor?.chain().focus().unsetLink().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Remove link')"
|
:title="t('Remove link')"
|
||||||
>
|
>
|
||||||
<o-icon icon="link-off" />
|
<LinkOff :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -99,9 +99,9 @@
|
|||||||
v-if="!isBasicMode"
|
v-if="!isBasicMode"
|
||||||
@click="showImagePrompt()"
|
@click="showImagePrompt()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Add picture')"
|
:title="t('Add picture')"
|
||||||
>
|
>
|
||||||
<o-icon icon="image" />
|
<Image :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -110,9 +110,9 @@
|
|||||||
:class="{ 'is-active': editor.isActive('bulletList') }"
|
:class="{ 'is-active': editor.isActive('bulletList') }"
|
||||||
@click="editor?.chain().focus().toggleBulletList().run()"
|
@click="editor?.chain().focus().toggleBulletList().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Bullet list')"
|
:title="t('Bullet list')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-list-bulleted" />
|
<FormatListBulleted :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -121,9 +121,9 @@
|
|||||||
:class="{ 'is-active': editor.isActive('orderedList') }"
|
:class="{ 'is-active': editor.isActive('orderedList') }"
|
||||||
@click="editor?.chain().focus().toggleOrderedList().run()"
|
@click="editor?.chain().focus().toggleOrderedList().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Ordered list')"
|
:title="t('Ordered list')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-list-numbered" />
|
<FormatListNumbered :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -132,9 +132,9 @@
|
|||||||
:class="{ 'is-active': editor.isActive('blockquote') }"
|
:class="{ 'is-active': editor.isActive('blockquote') }"
|
||||||
@click="editor?.chain().focus().toggleBlockquote().run()"
|
@click="editor?.chain().focus().toggleBlockquote().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Quote')"
|
:title="t('Quote')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-quote-close" />
|
<FormatQuoteClose :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -142,9 +142,9 @@
|
|||||||
class="menubar__button"
|
class="menubar__button"
|
||||||
@click="editor?.chain().focus().undo().run()"
|
@click="editor?.chain().focus().undo().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Undo')"
|
:title="t('Undo')"
|
||||||
>
|
>
|
||||||
<o-icon icon="undo" />
|
<Undo :size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -152,9 +152,9 @@
|
|||||||
class="menubar__button"
|
class="menubar__button"
|
||||||
@click="editor?.chain().focus().redo().run()"
|
@click="editor?.chain().focus().redo().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Redo')"
|
:title="t('Redo')"
|
||||||
>
|
>
|
||||||
<o-icon icon="redo" />
|
<Redo :size="24" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -169,10 +169,10 @@
|
|||||||
:class="{ 'is-active': editor.isActive('bold') }"
|
:class="{ 'is-active': editor.isActive('bold') }"
|
||||||
@click="editor?.chain().focus().toggleBold().run()"
|
@click="editor?.chain().focus().toggleBold().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Bold')"
|
:title="t('Bold')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-bold" />
|
<FormatBold :size="24" />
|
||||||
<span class="visually-hidden">{{ $t("Bold") }}</span>
|
<span class="visually-hidden">{{ t("Bold") }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -180,10 +180,10 @@
|
|||||||
:class="{ 'is-active': editor.isActive('italic') }"
|
:class="{ 'is-active': editor.isActive('italic') }"
|
||||||
@click="editor?.chain().focus().toggleItalic().run()"
|
@click="editor?.chain().focus().toggleItalic().run()"
|
||||||
type="button"
|
type="button"
|
||||||
:title="$t('Italic')"
|
:title="t('Italic')"
|
||||||
>
|
>
|
||||||
<o-icon icon="format-italic" />
|
<FormatItalic :size="24" />
|
||||||
<span class="visually-hidden">{{ $t("Italic") }}</span>
|
<span class="visually-hidden">{{ t("Italic") }}</span>
|
||||||
</button>
|
</button>
|
||||||
</bubble-menu>
|
</bubble-menu>
|
||||||
|
|
||||||
@ -223,6 +223,20 @@ import { Dialog } from "@/plugins/dialog";
|
|||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useMutation } from "@vue/apollo-composable";
|
import { useMutation } from "@vue/apollo-composable";
|
||||||
import { Notifier } from "@/plugins/notifier";
|
import { Notifier } from "@/plugins/notifier";
|
||||||
|
import FormatBold from "vue-material-design-icons/FormatBold.vue";
|
||||||
|
import FormatItalic from "vue-material-design-icons/FormatItalic.vue";
|
||||||
|
import FormatUnderline from "vue-material-design-icons/FormatUnderline.vue";
|
||||||
|
import FormatHeader1 from "vue-material-design-icons/FormatHeader1.vue";
|
||||||
|
import FormatHeader2 from "vue-material-design-icons/FormatHeader2.vue";
|
||||||
|
import FormatHeader3 from "vue-material-design-icons/FormatHeader3.vue";
|
||||||
|
import LinkIcon from "vue-material-design-icons/Link.vue";
|
||||||
|
import LinkOff from "vue-material-design-icons/LinkOff.vue";
|
||||||
|
import Image from "vue-material-design-icons/Image.vue";
|
||||||
|
import FormatListBulleted from "vue-material-design-icons/FormatListBulleted.vue";
|
||||||
|
import FormatListNumbered from "vue-material-design-icons/FormatListNumbered.vue";
|
||||||
|
import FormatQuoteClose from "vue-material-design-icons/FormatQuoteClose.vue";
|
||||||
|
import Undo from "vue-material-design-icons/Undo.vue";
|
||||||
|
import Redo from "vue-material-design-icons/Redo.vue";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@ -259,7 +273,7 @@ const isBasicMode = computed((): boolean => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const insertMention = (obj: { range: any; attrs: any }) => {
|
const insertMention = (obj: { range: any; attrs: any }) => {
|
||||||
console.log("initialize Mention");
|
console.debug("initialize Mention");
|
||||||
};
|
};
|
||||||
|
|
||||||
const observer = ref<MutationObserver | null>(null);
|
const observer = ref<MutationObserver | null>(null);
|
||||||
@ -421,7 +435,6 @@ onBeforeUnmount(() => {
|
|||||||
@import "./Editor/style.scss";
|
@import "./Editor/style.scss";
|
||||||
|
|
||||||
.menubar {
|
.menubar {
|
||||||
margin-bottom: 1rem;
|
|
||||||
transition: visibility 0.2s 0.4s, opacity 0.2s 0.4s;
|
transition: visibility 0.2s 0.4s, opacity 0.2s 0.4s;
|
||||||
|
|
||||||
&__button {
|
&__button {
|
@ -55,8 +55,8 @@ onDone(() => {
|
|||||||
onError((e) => {
|
onError((e) => {
|
||||||
// Snackbar.open({
|
// Snackbar.open({
|
||||||
// message: e.message,
|
// message: e.message,
|
||||||
// type: "is-danger",
|
// variant: "danger",
|
||||||
// position: "is-bottom",
|
// position: "bottom",
|
||||||
// });
|
// });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -85,7 +85,7 @@ updateTodoError((e) => {
|
|||||||
snackbar?.open({
|
snackbar?.open({
|
||||||
message: e.message,
|
message: e.message,
|
||||||
variant: "danger",
|
variant: "danger",
|
||||||
position: "is-bottom",
|
position: "bottom",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="flex mb-3" :aria-label="$t('Breadcrumbs')">
|
<nav class="flex mb-3" :aria-label="t('Breadcrumbs')">
|
||||||
<ol class="inline-flex items-center space-x-1 md:space-x-3 flex-wrap">
|
<ol class="inline-flex items-center space-x-1 md:space-x-3 flex-wrap">
|
||||||
<li
|
<li
|
||||||
class="inline-flex items-center"
|
class="inline-flex items-center"
|
||||||
@ -57,6 +57,7 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
import { RouteLocationRaw } from "vue-router";
|
import { RouteLocationRaw } from "vue-router";
|
||||||
|
|
||||||
type LinkElement = RouteLocationRaw & { text: string };
|
type LinkElement = RouteLocationRaw & { text: string };
|
||||||
@ -64,4 +65,6 @@ type LinkElement = RouteLocationRaw & { text: string };
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
links: LinkElement[];
|
links: LinkElement[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n({ useScope: "global" });
|
||||||
</script>
|
</script>
|
@ -6,7 +6,7 @@
|
|||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered">
|
||||||
<o-button
|
<o-button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="is-medium"
|
size="medium"
|
||||||
tag="router-link"
|
tag="router-link"
|
||||||
:to="{
|
:to="{
|
||||||
name: RouteName.LOGIN,
|
name: RouteName.LOGIN,
|
||||||
@ -46,7 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<o-button tag="a" type="is-text" @click="$router.go(-1)">{{
|
<o-button tag="a" variant="text" @click="$router.go(-1)">{{
|
||||||
$t("Back to previous page")
|
$t("Back to previous page")
|
||||||
}}</o-button>
|
}}</o-button>
|
||||||
</div>
|
</div>
|
||||||
@ -74,9 +74,9 @@ const host = computed((): string => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const redirectToInstance = async (): Promise<void> => {
|
const redirectToInstance = async (): Promise<void> => {
|
||||||
const [, host] = remoteActorAddress.value.split("@", 2);
|
const [, hostname] = remoteActorAddress.value.split("@", 2);
|
||||||
const remoteInteractionURI = await webFingerFetch(
|
const remoteInteractionURI = await webFingerFetch(
|
||||||
host,
|
hostname,
|
||||||
remoteActorAddress.value
|
remoteActorAddress.value
|
||||||
);
|
);
|
||||||
window.open(remoteInteractionURI);
|
window.open(remoteInteractionURI);
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
<template>
|
|
||||||
<component
|
|
||||||
:is="computedTag"
|
|
||||||
class="button"
|
|
||||||
v-bind="attrs"
|
|
||||||
:type="computedTag === 'button' ? nativeType : undefined"
|
|
||||||
:class="[
|
|
||||||
size,
|
|
||||||
type,
|
|
||||||
// {
|
|
||||||
// 'is-rounded': rounded,
|
|
||||||
// 'is-loading': loading,
|
|
||||||
// 'is-outlined': outlined,
|
|
||||||
// 'is-fullwidth': expanded,
|
|
||||||
// 'is-inverted': inverted,
|
|
||||||
// 'is-focused': focused,
|
|
||||||
// 'is-active': active,
|
|
||||||
// 'is-hovered': hovered,
|
|
||||||
// 'is-selected': selected,
|
|
||||||
// },
|
|
||||||
]"
|
|
||||||
v-on="attrs"
|
|
||||||
>
|
|
||||||
<!-- <o-icon
|
|
||||||
v-if="iconLeft"
|
|
||||||
:pack="iconPack"
|
|
||||||
:icon="iconLeft"
|
|
||||||
:size="iconSize"
|
|
||||||
/> -->
|
|
||||||
<span v-if="label">{{ label }}</span>
|
|
||||||
<span v-else-if="$slots.default">
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
<!-- <o-icon
|
|
||||||
v-if="iconRight"
|
|
||||||
:pack="iconPack"
|
|
||||||
:icon="iconRight"
|
|
||||||
:size="iconSize"
|
|
||||||
/> -->
|
|
||||||
</component>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, useAttrs } from "vue";
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
type?: string;
|
|
||||||
size?: string;
|
|
||||||
label?: string;
|
|
||||||
nativeType?: "button" | "submit" | "reset";
|
|
||||||
tag?: "button" | "a" | "router-link";
|
|
||||||
}>(),
|
|
||||||
{ tag: "button" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const attrs = useAttrs();
|
|
||||||
|
|
||||||
const computedTag = computed(() => {
|
|
||||||
if (attrs.disabled !== undefined && attrs.disabled !== false) {
|
|
||||||
return "button";
|
|
||||||
}
|
|
||||||
return props.tag;
|
|
||||||
});
|
|
||||||
|
|
||||||
const iconSize = computed(() => {
|
|
||||||
if (!props.size || props.size === "is-medium") {
|
|
||||||
return "is-small";
|
|
||||||
} else if (props.size === "is-large") {
|
|
||||||
return "is-medium";
|
|
||||||
}
|
|
||||||
return props.size;
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -63,8 +63,8 @@ const props = withDefaults(
|
|||||||
canCancel?: boolean;
|
canCancel?: boolean;
|
||||||
confirmText?: string;
|
confirmText?: string;
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
onConfirm: (prompt?: string) => {};
|
onConfirm: (prompt?: string) => any;
|
||||||
onCancel?: (source: string) => {};
|
onCancel?: (source: string) => any;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
ariaModal?: boolean;
|
ariaModal?: boolean;
|
||||||
ariaRole?: string;
|
ariaRole?: string;
|
@ -1,33 +0,0 @@
|
|||||||
<template>
|
|
||||||
<label :for="labelFor" class="block mb-2">
|
|
||||||
<span class="font-bold mb-2 block">
|
|
||||||
{{ label }}
|
|
||||||
</span>
|
|
||||||
<slot :type="type" />
|
|
||||||
<template v-if="Array.isArray(message) && message.length > 0">
|
|
||||||
<p v-for="msg in message" :key="msg" :class="classNames">
|
|
||||||
{{ msg }}
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
<p v-else-if="typeof message === 'string'" :class="classNames">
|
|
||||||
{{ message }}
|
|
||||||
</p>
|
|
||||||
</label>
|
|
||||||
</template>
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed } from "vue";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
label: string;
|
|
||||||
type?: string;
|
|
||||||
message?: string | string[];
|
|
||||||
labelFor?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const classNames = computed(() => {
|
|
||||||
switch (props.type) {
|
|
||||||
case "is-danger":
|
|
||||||
return "text-red-600";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -1,292 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="control" :class="rootClasses">
|
|
||||||
<input
|
|
||||||
v-if="type !== 'textarea'"
|
|
||||||
ref="input"
|
|
||||||
class="input"
|
|
||||||
:class="[inputClasses, customClass]"
|
|
||||||
:type="newType"
|
|
||||||
:autocomplete="autocomplete"
|
|
||||||
:maxlength="maxLength"
|
|
||||||
:value="computedValue"
|
|
||||||
v-bind="$attrs"
|
|
||||||
@input="onInput"
|
|
||||||
@blur="onBlur"
|
|
||||||
@focus="onFocus"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<textarea
|
|
||||||
v-else
|
|
||||||
ref="textarea"
|
|
||||||
class="textarea"
|
|
||||||
:class="[inputClasses, customClass]"
|
|
||||||
:maxlength="maxLength"
|
|
||||||
:value="computedValue"
|
|
||||||
v-bind="$attrs"
|
|
||||||
@input="onInput"
|
|
||||||
@blur="onBlur"
|
|
||||||
@focus="onFocus"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- <o-icon
|
|
||||||
v-if="icon"
|
|
||||||
class="is-left"
|
|
||||||
:icon="icon"
|
|
||||||
:size="iconSize"
|
|
||||||
@click.native="emit('icon-click', $event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<o-icon
|
|
||||||
v-if="!loading && hasIconRight"
|
|
||||||
class="is-right"
|
|
||||||
:class="{ 'is-clickable': passwordReveal }"
|
|
||||||
:icon="rightIcon"
|
|
||||||
:size="iconSize"
|
|
||||||
:type="rightIconType"
|
|
||||||
both
|
|
||||||
@click.native="rightIconClick"
|
|
||||||
/> -->
|
|
||||||
|
|
||||||
<small
|
|
||||||
v-if="maxLength && hasCounter && type !== 'number'"
|
|
||||||
class="help counter"
|
|
||||||
:class="{ 'is-invisible': !isFocused }"
|
|
||||||
>
|
|
||||||
{{ valueLength }} / {{ maxLength }}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, nextTick, ref, watch } from "vue";
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
icon?: string;
|
|
||||||
modelValue: number | string;
|
|
||||||
size?: string;
|
|
||||||
type?: string;
|
|
||||||
passwordReveal?: boolean;
|
|
||||||
iconRight?: string;
|
|
||||||
rounded?: boolean;
|
|
||||||
loading?: boolean;
|
|
||||||
customClass?: string;
|
|
||||||
maxLength?: number | string;
|
|
||||||
hasCounter?: boolean;
|
|
||||||
autocomplete?: "on" | "off";
|
|
||||||
statusType?: string;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
rounded: false,
|
|
||||||
loading: false,
|
|
||||||
customClass: "",
|
|
||||||
hasCounter: false,
|
|
||||||
autocomplete: "on",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue", "icon-click", "blur", "focus"]);
|
|
||||||
|
|
||||||
const newValue = ref(props.modelValue);
|
|
||||||
const newType = ref(props.type);
|
|
||||||
const isPasswordVisible = ref(false);
|
|
||||||
const isValid = ref(true);
|
|
||||||
const isFocused = ref(false);
|
|
||||||
|
|
||||||
const computedValue = computed({
|
|
||||||
get() {
|
|
||||||
return newValue.value;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
newValue.value = value;
|
|
||||||
emit("update:modelValue", value);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const rootClasses = computed(() => {
|
|
||||||
return [
|
|
||||||
iconPosition,
|
|
||||||
props.size,
|
|
||||||
// {
|
|
||||||
// 'is-expanded': this.expanded,
|
|
||||||
// 'is-loading': this.loading,
|
|
||||||
// 'is-clearfix': !this.hasMessage
|
|
||||||
// }
|
|
||||||
];
|
|
||||||
});
|
|
||||||
const inputClasses = computed(() => {
|
|
||||||
return [props.statusType, props.size, { "is-rounded": props.rounded }];
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasIconRight = computed(() => {
|
|
||||||
return (
|
|
||||||
props.passwordReveal || props.loading || statusTypeIcon || props.iconRight
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const rightIcon = computed(() => {
|
|
||||||
if (props.passwordReveal) {
|
|
||||||
return passwordVisibleIcon;
|
|
||||||
} else if (props.iconRight) {
|
|
||||||
return props.iconRight;
|
|
||||||
}
|
|
||||||
return statusTypeIcon;
|
|
||||||
});
|
|
||||||
const rightIconType = computed(() => {
|
|
||||||
if (props.passwordReveal) {
|
|
||||||
return "is-primary";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* Position of the icon or if it's both sides.
|
|
||||||
*/
|
|
||||||
const iconPosition = computed(() => {
|
|
||||||
let iconClasses = "";
|
|
||||||
if (props.icon) {
|
|
||||||
iconClasses += "has-icons-left ";
|
|
||||||
}
|
|
||||||
if (hasIconRight.value) {
|
|
||||||
iconClasses += "has-icons-right";
|
|
||||||
}
|
|
||||||
return iconClasses;
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* Icon name (MDI) based on the type.
|
|
||||||
*/
|
|
||||||
const statusTypeIcon = computed(() => {
|
|
||||||
switch (props.statusType) {
|
|
||||||
case "is-success":
|
|
||||||
return "check";
|
|
||||||
case "is-danger":
|
|
||||||
return "alert-circle";
|
|
||||||
case "is-info":
|
|
||||||
return "information";
|
|
||||||
case "is-warning":
|
|
||||||
return "alert";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* Current password-reveal icon name.
|
|
||||||
*/
|
|
||||||
const passwordVisibleIcon = computed(() => {
|
|
||||||
return !isPasswordVisible.value ? "eye" : "eye-off";
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* Get value length
|
|
||||||
*/
|
|
||||||
const valueLength = computed(() => {
|
|
||||||
if (typeof computedValue.value === "string") {
|
|
||||||
return Array.from(computedValue.value).length;
|
|
||||||
} else if (typeof computedValue.value === "number") {
|
|
||||||
return computedValue.value.toString().length;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fix icon size for inputs, large was too big
|
|
||||||
*/
|
|
||||||
const iconSize = computed(() => {
|
|
||||||
switch (props.size) {
|
|
||||||
case "is-small":
|
|
||||||
return props.size;
|
|
||||||
case "is-medium":
|
|
||||||
return;
|
|
||||||
case "is-large":
|
|
||||||
return "is-medium";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(props, () => {
|
|
||||||
newValue.value = props.modelValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the visibility of a password-reveal input
|
|
||||||
* by changing the type and focus the input right away.
|
|
||||||
*/
|
|
||||||
const togglePasswordVisibility = async () => {
|
|
||||||
isPasswordVisible.value = !isPasswordVisible.value;
|
|
||||||
newType.value = isPasswordVisible.value ? "text" : "password";
|
|
||||||
await nextTick();
|
|
||||||
await focus();
|
|
||||||
};
|
|
||||||
const rightIconClick = (event: Event) => {
|
|
||||||
if (props.passwordReveal) {
|
|
||||||
togglePasswordVisibility();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const onInput = (event: Event) => {
|
|
||||||
const value = event.target?.value;
|
|
||||||
updateValue(value);
|
|
||||||
};
|
|
||||||
const updateValue = (value: string) => {
|
|
||||||
computedValue.value = value;
|
|
||||||
!isValid.value && checkHtml5Validity();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check HTML5 validation, set isValid property.
|
|
||||||
* If validation fail, send 'is-danger' type,
|
|
||||||
* and error message to parent if it's a Field.
|
|
||||||
*/
|
|
||||||
const checkHtml5Validity = () => {
|
|
||||||
const el = getElement();
|
|
||||||
if (el === undefined) return;
|
|
||||||
|
|
||||||
if (!el.value?.checkValidity()) {
|
|
||||||
// setInvalid();
|
|
||||||
isValid.value = false;
|
|
||||||
} else {
|
|
||||||
// setValidity(null, null);
|
|
||||||
isValid.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isValid.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// const setInvalid = () => {
|
|
||||||
// let type = "is-danger";
|
|
||||||
// let message = validationMessage || getElement().validationMessage;
|
|
||||||
// setValidity(type, message);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const setValidity = async (type, message) => {
|
|
||||||
// await nextTick();
|
|
||||||
// if (this.parentField) {
|
|
||||||
// // Set type only if not defined
|
|
||||||
// if (!this.parentField.type) {
|
|
||||||
// this.parentField.newType = type;
|
|
||||||
// }
|
|
||||||
// // Set message only if not defined
|
|
||||||
// if (!this.parentField.message) {
|
|
||||||
// this.parentField.newMessage = message;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const input = ref<HTMLInputElement | null>(null);
|
|
||||||
const textarea = ref<HTMLInputElement | null>(null);
|
|
||||||
|
|
||||||
const getElement = () => {
|
|
||||||
return props.type === "input" ? input : textarea;
|
|
||||||
};
|
|
||||||
|
|
||||||
const focus = async () => {
|
|
||||||
const el = getElement();
|
|
||||||
if (el.value === undefined) return;
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
if (el.value) el.value?.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onBlur = ($event: FocusEvent) => {
|
|
||||||
isFocused.value = false;
|
|
||||||
emit("blur", $event);
|
|
||||||
checkHtml5Validity();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFocus = ($event: FocusEvent) => {
|
|
||||||
isFocused.value = true;
|
|
||||||
emit("focus", $event);
|
|
||||||
};
|
|
||||||
</script>
|
|
39
js/src/components/core/LinkOrRouterLink.vue
Normal file
39
js/src/components/core/LinkOrRouterLink.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<a
|
||||||
|
v-if="isInternal"
|
||||||
|
:target="newTab ? '_blank' : undefined"
|
||||||
|
:href="href"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</a>
|
||||||
|
<router-link :to="to" v-bind="$attrs" v-else>
|
||||||
|
<slot />
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
// use normal <script> to declare options
|
||||||
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
to: { name: string; params?: any; query?: any } | string;
|
||||||
|
isInternal?: boolean;
|
||||||
|
newTab?: boolean;
|
||||||
|
}>(),
|
||||||
|
{ isInternal: true, newTab: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const href = computed(() => {
|
||||||
|
if (typeof props.to === "string" || props.to instanceof String) {
|
||||||
|
return props.to as string;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
</script>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user