@saooti/octopus-sdk 41.2.1 → 41.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -1
- package/doc/README.md +5 -0
- package/doc/routing.md +50 -0
- package/index.ts +8 -1
- package/package.json +6 -2
- package/src/App.vue +7 -36
- package/src/api/organisationApi.ts +15 -2
- package/src/api/podcastApi.ts +1 -8
- package/src/components/composable/player/usePlayerLogic.ts +2 -2
- package/src/components/composable/route/useSeoTitleUrl.ts +2 -1
- package/src/components/composable/share/useSharePath.ts +77 -0
- package/src/components/composable/share/useSharePlateforms.ts +168 -0
- package/src/components/composable/useImageProxy.ts +3 -3
- package/src/components/composable/useMetaTitle.ts +5 -4
- package/src/components/display/emission/EmissionList.vue +1 -0
- package/src/components/display/emission/EmissionPresentationItem.vue +1 -1
- package/src/components/display/emission/EmissionPresentationList.vue +1 -1
- package/src/components/display/filter/AdvancedSearch.vue +1 -1
- package/src/components/display/podcasts/PodcastItem.vue +3 -1
- package/src/components/display/podcasts/PodcastList.vue +1 -1
- package/src/components/display/podcasts/PodcastModuleBox.vue +1 -1
- package/src/components/display/podcasts/PodcastPresentationList.vue +2 -2
- package/src/components/display/sharing/PlayerParameters.vue +16 -2
- package/src/components/display/sharing/ShareNewsletter.vue +6 -5
- package/src/components/display/sharing/SharePlayer.vue +7 -0
- package/src/components/display/sharing/SubscribeButtons.vue +19 -117
- package/src/components/form/ClassicInputText.vue +14 -4
- package/src/components/icons/CastboxIcon.vue +20 -0
- package/src/components/icons/PodbeanIcon.vue +27 -0
- package/src/components/icons/PodcastRepublicIcon.vue +63 -0
- package/src/components/misc/TopBar.vue +7 -3
- package/src/components/misc/modal/MessageModal.vue +2 -1
- package/src/components/pages/EmissionPage.vue +4 -6
- package/src/components/pages/PlaylistPage.vue +3 -5
- package/src/components/pages/PodcastPage.vue +6 -5
- package/src/components/pages/SmartLinkPage.vue +381 -0
- package/src/helper/displayHelper.ts +19 -4
- package/src/layouts/FullLayout.vue +48 -0
- package/src/layouts/SimpleLayout.vue +25 -0
- package/src/locale/{de.ts → de.json} +199 -192
- package/src/locale/{en.ts → en.json} +187 -183
- package/src/locale/{es.ts → es.json} +200 -194
- package/src/locale/{fr.ts → fr.json} +96 -76
- package/src/locale/it.json +431 -0
- package/src/locale/messages.ts +9 -6
- package/src/locale/{sl.ts → sl.json} +190 -176
- package/src/router/router.ts +9 -1
- package/src/router/routes.ts +29 -10
- package/src/router/utils.ts +31 -1
- package/src/stores/FilterStore.ts +17 -1
- package/src/stores/PlayerStore.ts +14 -10
- package/src/stores/class/general/emission.ts +2 -1
- package/src/stores/class/general/index.ts +4 -0
- package/src/stores/class/general/organisation.ts +7 -1
- package/src/stores/class/general/playlist.ts +6 -0
- package/src/stores/class/general/podcast.ts +3 -0
- package/tests/components/pages/SmartLinkPage.spec.ts +90 -0
- package/tests/utils.ts +75 -0
- package/tsconfig.json +44 -40
- package/vitest.config.js +18 -0
- package/src/locale/it.ts +0 -420
- /package/src/components/{layout → layouts}/PresentationItem.vue +0 -0
- /package/src/components/{layout → layouts}/PresentationLayout.vue +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,54 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 42.3.1 (09/01/2026)
|
|
4
|
+
|
|
5
|
+
**Features**
|
|
6
|
+
|
|
7
|
+
- Recherche avancée
|
|
8
|
+
- Ajout du filtrage par groupe sur les émissions
|
|
9
|
+
- Ajustement filtrage par groupe sur les podcasts
|
|
10
|
+
- Miniplayer
|
|
11
|
+
- Ajout d'une option de hauteur automatique
|
|
12
|
+
- Les liens dans les descriptions s'ouvrent maintenant dans un nouvel onglet
|
|
13
|
+
|
|
14
|
+
**Fixes**
|
|
15
|
+
|
|
16
|
+
- Suppression d'un lien SmartLink oublié sur les playlists.
|
|
17
|
+
- Correction d'affichage des descriptions html dans les SmartLinks
|
|
18
|
+
|
|
19
|
+
**Misc**
|
|
20
|
+
|
|
21
|
+
- Producteur mis à jour lors de la consultation d'une émission/playlist/podcast
|
|
22
|
+
quand non connecté
|
|
23
|
+
|
|
24
|
+
## 42.3.0 (09/01/2026)
|
|
25
|
+
|
|
26
|
+
**Features**
|
|
27
|
+
|
|
28
|
+
- Affichage des flux RSS définis sur les playlists
|
|
29
|
+
- Ajout d'une page de smartlink pour les émissions et les playlists
|
|
30
|
+
- Affiche les informations de base de l'émission/la playlist
|
|
31
|
+
- Affiche les liens vers les différents diffuseurs définis
|
|
32
|
+
- Affiche un bouton de lecture du dernier épisode
|
|
33
|
+
- Affiche un lien vers le podcastmaker, si défini
|
|
34
|
+
- Mise en place d'un système de layouts (cf [documentation](./doc/routing.md))
|
|
35
|
+
- Plateformes de distribution
|
|
36
|
+
- Factorisation du code pour simplifier la réutilisation
|
|
37
|
+
- Mise en place du composable `useSharePlatforms`
|
|
38
|
+
- Ajout de PodBean, Podcast Republic, et Castbox
|
|
39
|
+
- Ajout du composable `useSharePath`
|
|
40
|
+
- Regroupe la logique pour le calcul des paths pour les liens partagés
|
|
41
|
+
|
|
42
|
+
**Misc**
|
|
43
|
+
|
|
44
|
+
- Début de [doc](./doc/README.md)
|
|
45
|
+
- Début mise en place de tests unitaires
|
|
46
|
+
|
|
3
47
|
## 41.2.1 (05/01/2026)
|
|
4
48
|
|
|
5
49
|
**Fixes**
|
|
6
50
|
|
|
7
|
-
- Recherche
|
|
51
|
+
- Recherche avancée
|
|
8
52
|
- Correction chargements groupes & ayants-droits depuis routing
|
|
9
53
|
- Filtrage des groupes par organisation
|
|
10
54
|
|
package/doc/README.md
ADDED
package/doc/routing.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Routing
|
|
2
|
+
|
|
3
|
+
octopus-sdk se base sur `vue-router` pour mettre en place le routing de
|
|
4
|
+
l'application.
|
|
5
|
+
|
|
6
|
+
Il est cependant nécessaire de faire une mise en place particulière pour
|
|
7
|
+
le bon fonctionnement de l'application.
|
|
8
|
+
|
|
9
|
+
## Mise en place du routing
|
|
10
|
+
|
|
11
|
+
Les routes sont définies de manière standard dans octopus-sdk, dans le fichier
|
|
12
|
+
`src/router/routes.ts`. Ces routes sont exportées, afin de pouvoir être
|
|
13
|
+
intégrées dans les applications s'appuyant sur le sdk.
|
|
14
|
+
|
|
15
|
+
Afin d'appliquer la logique propre aux routes, il est nécessaire d'appeler la
|
|
16
|
+
fonction utilitaire `setupRouter` (définie dans `src/router/utils.ts`).
|
|
17
|
+
Celle-ci se charge de mettre en place des navigation guards, en particulier
|
|
18
|
+
pour la gestion du productor actif.
|
|
19
|
+
|
|
20
|
+
Un exemple d'appel est disponible dans `src/router/router.ts`.
|
|
21
|
+
|
|
22
|
+
## Layouts
|
|
23
|
+
|
|
24
|
+
octopus-sdk inclu un système de layouts permettant de simplement modifier la
|
|
25
|
+
façon dont les pages sont affichées.
|
|
26
|
+
|
|
27
|
+
Par défaut, les pages utilisent le layout défini lors de l'appel à
|
|
28
|
+
`setupRouter`.
|
|
29
|
+
|
|
30
|
+
Il est cependant possible d'écraser ce layout en rajoutant une propriété
|
|
31
|
+
`layout` aux `meta` de la route. Cela permet par exemple de créer une page
|
|
32
|
+
n'affichage pas le header ou le footer standard de l'application.
|
|
33
|
+
|
|
34
|
+
Exemple d'usage :
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
{
|
|
38
|
+
path: '/my/new/page',
|
|
39
|
+
component: Page,
|
|
40
|
+
meta: {
|
|
41
|
+
title: 'Title of page with custom layout',
|
|
42
|
+
layout: () => import('../layouts/MyCustomLayout.vue')
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Dans ce cas, la page `/my/new/page` n'utilisera pas le layout par défaut,
|
|
48
|
+
mais celui de `../layouts/MyCustomLayout.vue`.
|
|
49
|
+
|
|
50
|
+
Des exemples de layouts sont définis dans `src/layouts`.
|
package/index.ts
CHANGED
|
@@ -127,6 +127,8 @@ import {useInit} from "./src/components/composable/useInit.ts";
|
|
|
127
127
|
import {useErrorHandler} from "./src/components/composable/useErrorHandler.ts";
|
|
128
128
|
import { useSimplePageParam } from "./src/components/composable/route/useSimplePageParam";
|
|
129
129
|
import { useNotifications } from "./src/components/composable/useNotifications.ts";
|
|
130
|
+
export { useSharePlatforms } from "./src/components/composable/share/useSharePlateforms.ts";
|
|
131
|
+
export { useSharePath } from "./src/components/composable/share/useSharePath.ts";
|
|
130
132
|
|
|
131
133
|
//helper
|
|
132
134
|
import domHelper from "./src/helper/domHelper.ts";
|
|
@@ -160,6 +162,9 @@ export { podcastApi, PodcastSort, type PodcastSearchOptions } from "./src/api/po
|
|
|
160
162
|
|
|
161
163
|
// Types
|
|
162
164
|
export { type Emission, emptyEmissionData } from "./src/stores/class/general/emission.ts";
|
|
165
|
+
export { type Podcast, type PodcastAvailability } from "./src/stores/class/general/podcast.ts";
|
|
166
|
+
export { type Playlist, type PlaylistRule } from "./src/stores/class/general/playlist.ts";
|
|
167
|
+
export { type Annotations } from "./src/stores/class/general";
|
|
163
168
|
|
|
164
169
|
//Icons
|
|
165
170
|
export const getAmazonMusicIcon = () => import("./src/components/icons/AmazonMusicIcon.vue");
|
|
@@ -175,6 +180,9 @@ export const getRadiolineIcon = () => import("./src/components/icons/RadiolineIc
|
|
|
175
180
|
export const getTuninIcon = () => import("./src/components/icons/TuninIcon.vue");
|
|
176
181
|
export const getXIcon = () => import("./src/components/icons/XIcon.vue");
|
|
177
182
|
|
|
183
|
+
// Layouts
|
|
184
|
+
export const getSimpleLayout = () => import("./src/layouts/SimpleLayout.vue");
|
|
185
|
+
|
|
178
186
|
// Routing
|
|
179
187
|
import { setupRouter, getSimpleRouteProps, getRouteProps } from './src/router/utils';
|
|
180
188
|
import { routes as sdkRoutes } from './src/router/routes';
|
|
@@ -183,7 +191,6 @@ import { routes as sdkRoutes } from './src/router/routes';
|
|
|
183
191
|
import { type SelectOption } from "./src/components/form/ClassicSelect.vue";
|
|
184
192
|
|
|
185
193
|
import { ROUTE_PARAMS } from "./src/components/composable/route/types";
|
|
186
|
-
import { defineAsyncComponent } from "vue";
|
|
187
194
|
|
|
188
195
|
export {
|
|
189
196
|
useResizePhone,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saooti/octopus-sdk",
|
|
3
|
-
"version": "41.
|
|
3
|
+
"version": "41.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Javascript SDK for using octopus",
|
|
6
6
|
"author": "Saooti",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"serve": "vite preview",
|
|
9
9
|
"build": "vite build",
|
|
10
10
|
"dev": "vite",
|
|
11
|
+
"test": "vitest",
|
|
11
12
|
"bundle": "vite-bundle-visualizer",
|
|
12
13
|
"proxy_authentifié": "node proxy.ts",
|
|
13
14
|
"proxy_non_authentifié": "node proxy.ts false",
|
|
@@ -66,9 +67,12 @@
|
|
|
66
67
|
"@types/sockjs-client": "^1.5.4",
|
|
67
68
|
"@types/webpack-env": "^1.18.8",
|
|
68
69
|
"@vitejs/plugin-vue": "^5.2.4",
|
|
70
|
+
"@vue/test-utils": "^2.4.6",
|
|
69
71
|
"eslint": "^9.39.1",
|
|
70
72
|
"eslint-plugin-vue": "^9.33.0",
|
|
71
|
-
"
|
|
73
|
+
"happy-dom": "^20.1.0",
|
|
74
|
+
"typescript": "^5.9.3",
|
|
75
|
+
"vitest": "^4.0.16"
|
|
72
76
|
},
|
|
73
77
|
"postcss": {
|
|
74
78
|
"plugins": {
|
package/src/App.vue
CHANGED
|
@@ -1,37 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="d-flex flex-column h-100 octopus-app">
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
<CategoryFilter v-if="firstDisplayCategoryFilter" />
|
|
7
|
-
<div v-else class="category-filter-no-filter" />
|
|
8
|
-
<router-view />
|
|
9
|
-
<PlayerComponent />
|
|
10
|
-
</main>
|
|
11
|
-
<FooterOctopus />
|
|
12
|
-
</template>
|
|
3
|
+
<component v-if="pageFullyLoad" :is="route.meta.layoutComponent">
|
|
4
|
+
<router-view />
|
|
5
|
+
</component>
|
|
13
6
|
</div>
|
|
14
7
|
</template>
|
|
8
|
+
|
|
15
9
|
<script setup lang="ts">
|
|
16
|
-
import TopBar from "@/components/misc/TopBar.vue";
|
|
17
|
-
import FooterOctopus from "@/components/misc/FooterSection.vue";
|
|
18
|
-
import PlayerComponent from "@/components/misc/player/PlayerComponent.vue";
|
|
19
10
|
import {useInit} from "./components/composable/useInit";
|
|
20
11
|
import {useMetaTitle} from "./components/composable/useMetaTitle";
|
|
21
12
|
import {useOrganisationFilter} from "./components/composable/useOrganisationFilter";
|
|
22
13
|
import { useAuthStore } from "./stores/AuthStore";
|
|
23
|
-
import {
|
|
14
|
+
import { getCurrentInstance, onBeforeMount, ref, watch } from "vue";
|
|
24
15
|
import { useRoute } from "vue-router";
|
|
25
16
|
import { useI18n } from "vue-i18n";
|
|
26
17
|
|
|
27
|
-
const CategoryFilter = defineAsyncComponent(
|
|
28
|
-
() => import("@/components/display/categories/CategoryFilter.vue"),
|
|
29
|
-
);
|
|
30
|
-
|
|
31
18
|
//Data
|
|
32
19
|
const reload = ref(false);
|
|
33
20
|
const pageFullyLoad = ref(false);
|
|
34
|
-
const firstDisplayCategoryFilter = ref(false);
|
|
35
21
|
|
|
36
22
|
//Composables
|
|
37
23
|
const {locale} = useI18n();
|
|
@@ -41,25 +27,11 @@ const {selectOrganisation} = useOrganisationFilter();
|
|
|
41
27
|
const authStore = useAuthStore();
|
|
42
28
|
const route = useRoute();
|
|
43
29
|
|
|
44
|
-
|
|
45
30
|
//Watch
|
|
46
31
|
watch(route, async () => {
|
|
47
32
|
updateMetaTitle();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
const namesRouteWithCategoryFilter = [
|
|
52
|
-
"homePriv",
|
|
53
|
-
"home",
|
|
54
|
-
"podcasts",
|
|
55
|
-
"emissions",
|
|
56
|
-
"participants",
|
|
57
|
-
"playlists",
|
|
58
|
-
];
|
|
59
|
-
firstDisplayCategoryFilter.value = namesRouteWithCategoryFilter.includes(
|
|
60
|
-
route.name?.toString() ?? "",
|
|
61
|
-
);
|
|
62
|
-
}, {immediate: true});
|
|
33
|
+
}, { immediate: true });
|
|
34
|
+
|
|
63
35
|
watch(locale,() => {
|
|
64
36
|
updateMetaTitle();
|
|
65
37
|
const instance = getCurrentInstance();
|
|
@@ -67,7 +39,6 @@ watch(locale,() => {
|
|
|
67
39
|
reload.value = !reload.value;
|
|
68
40
|
});
|
|
69
41
|
|
|
70
|
-
|
|
71
42
|
onBeforeMount(()=>{
|
|
72
43
|
initApp();
|
|
73
44
|
setTimeout(() => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import classicApi from "./classicApi";
|
|
2
2
|
import { ModuleApi } from "./apiConnection";
|
|
3
|
-
import { Organisation } from "../stores/class/general/organisation";
|
|
3
|
+
import { Organisation, OrganisationAttributes } from "../stores/class/general/organisation";
|
|
4
4
|
import { mapFromGetAll } from "./apiUtils";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -25,7 +25,20 @@ async function getAllById(organisationIds: Array<string>): Promise<Record<string
|
|
|
25
25
|
return mapFromGetAll(organisationIds, get, 'id');
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Retrieve the attributes of a given organisation
|
|
30
|
+
* @param organisationId The ID of the organisation
|
|
31
|
+
* @returns The attributes of the organisation
|
|
32
|
+
*/
|
|
33
|
+
async function getAttributes(organisationId: string): Promise<OrganisationAttributes> {
|
|
34
|
+
return classicApi.fetchData<OrganisationAttributes>({
|
|
35
|
+
api: ModuleApi.DEFAULT,
|
|
36
|
+
path: 'organisation/attributes/' + organisationId
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
export const organisationApi = {
|
|
29
41
|
get,
|
|
30
|
-
getAllById
|
|
42
|
+
getAllById,
|
|
43
|
+
getAttributes
|
|
31
44
|
};
|
package/src/api/podcastApi.ts
CHANGED
|
@@ -8,7 +8,6 @@ import { organisationApi } from './organisationApi';
|
|
|
8
8
|
import { emissionApi } from './emissionApi';
|
|
9
9
|
import { EmissionGroup } from './groupsApi';
|
|
10
10
|
import { FetchParam } from '@/stores/class/general/fetchParam';
|
|
11
|
-
import { toRaw } from 'vue';
|
|
12
11
|
|
|
13
12
|
export enum PodcastSort {
|
|
14
13
|
DATE = 'DATE',
|
|
@@ -45,7 +44,7 @@ export interface PodcastSearchOptions extends Paginable<PodcastSort> {
|
|
|
45
44
|
/** Filter by emission ID */
|
|
46
45
|
emissionId?: number|number[];
|
|
47
46
|
/** Filter by emission groups */
|
|
48
|
-
|
|
47
|
+
groupId?: number[];
|
|
49
48
|
/** Filter by organisation ID */
|
|
50
49
|
organisationId?: string[];
|
|
51
50
|
/** Filter by title containing */
|
|
@@ -135,12 +134,6 @@ function processSearchParameters(search: PodcastSearchOptions): FetchParam {
|
|
|
135
134
|
parameters.after = value;
|
|
136
135
|
} else if (key === 'pageSize') {
|
|
137
136
|
parameters.size = value;
|
|
138
|
-
} else if (key === 'emissionGroups') {
|
|
139
|
-
const emissionIds = [search.emissionId ?? undefined].flat();
|
|
140
|
-
search.emissionGroups.forEach(group => {
|
|
141
|
-
emissionIds.push(...group.emissionIds);
|
|
142
|
-
});
|
|
143
|
-
parameters.emissionId = emissionIds;
|
|
144
137
|
} else {
|
|
145
138
|
parameters[key] = value;
|
|
146
139
|
}
|
|
@@ -260,7 +260,7 @@ export const usePlayerLogic = (forceHide: Ref<boolean, boolean>) => {
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
|
|
263
|
-
|
|
263
|
+
return {
|
|
264
264
|
audioUrlToPlay,
|
|
265
265
|
listenTime,
|
|
266
266
|
playerError,
|
|
@@ -275,5 +275,5 @@ export const usePlayerLogic = (forceHide: Ref<boolean, boolean>) => {
|
|
|
275
275
|
onSeeked,
|
|
276
276
|
onFinished,
|
|
277
277
|
onPlay
|
|
278
|
-
|
|
278
|
+
}
|
|
279
279
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { OrganisationAttributes } from "@/stores/class/general/organisation";
|
|
2
|
+
import { RouteLocation, RouteLocationAsRelativeTyped, RouteLocationNormalized, useRouter } from "vue-router";
|
|
3
|
+
import { useSeoTitleUrl } from "../route/useSeoTitleUrl";
|
|
4
|
+
|
|
5
|
+
export const useSharePath = () => {
|
|
6
|
+
const router = useRouter();
|
|
7
|
+
const { stringUrlEncode } = useSeoTitleUrl();
|
|
8
|
+
|
|
9
|
+
/** Retrieve the URL of the podcastmaker, if any */
|
|
10
|
+
function getPodcastMakerUrl(attributes?: OrganisationAttributes): string|undefined {
|
|
11
|
+
if (attributes?.podcastmakerUrl) {
|
|
12
|
+
return attributes.podcastmakerUrl;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Retrieve the base URL for sharing.
|
|
18
|
+
* If the podcastmaker URL is defined, it will be used.
|
|
19
|
+
* Otherwise, the frontend URL will be used.
|
|
20
|
+
* @param attributes The attributes defined on the organisation, from
|
|
21
|
+
which the podcastmaker url is extracted
|
|
22
|
+
*/
|
|
23
|
+
function getBaseSharePath(attributes?: OrganisationAttributes): string {
|
|
24
|
+
const pmUrl = getPodcastMakerUrl(attributes);
|
|
25
|
+
if (pmUrl) {
|
|
26
|
+
return pmUrl;
|
|
27
|
+
} else {
|
|
28
|
+
return window.location.origin;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Retrieve the full URL to a page, for sharing.
|
|
34
|
+
* If the podcastmaker URL is set, the link will point to it.
|
|
35
|
+
* @param attributes The attributes defined on the organisation, from
|
|
36
|
+
which the podcastmaker url is extracted
|
|
37
|
+
*/
|
|
38
|
+
function getSharePath(route: RouteLocationAsRelativeTyped, attributes?: OrganisationAttributes): string {
|
|
39
|
+
const resolved = router.resolve(route);
|
|
40
|
+
return getBaseSharePath(attributes) + resolved.path;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type GetSmartLinkParam = {
|
|
44
|
+
/** The title of the element displayed by the smartlink page */
|
|
45
|
+
title?: string;
|
|
46
|
+
playlistId: number;
|
|
47
|
+
emissionId?: never;
|
|
48
|
+
} | {
|
|
49
|
+
/** The title of the element displayed by the smartlink page */
|
|
50
|
+
title?: string;
|
|
51
|
+
playlistId?: never;
|
|
52
|
+
emissionId: number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Retrieve the full URL for SmartLinks.
|
|
56
|
+
* If the podcastmaker URL is set, the link will point to it.
|
|
57
|
+
* @param params The parameters of the smartlink. The ID of the element is
|
|
58
|
+
required, whereas the title is optional, but if specified
|
|
59
|
+
it will be included in the URL.
|
|
60
|
+
* @param attributes The attributes defined on the organisation, from
|
|
61
|
+
which the podcastmaker url is extracted
|
|
62
|
+
*/
|
|
63
|
+
function getSmartLink(params: GetSmartLinkParam, attributes?: OrganisationAttributes): string {
|
|
64
|
+
const title = params.title ? stringUrlEncode(params.title) : undefined;
|
|
65
|
+
if (params.playlistId) {
|
|
66
|
+
return getSharePath({ name: 'playlist-smartlink', params: { playlistId: params.playlistId, title }}, attributes);
|
|
67
|
+
} else if (params.emissionId) {
|
|
68
|
+
return getSharePath({ name: 'emission-smartlink', params: { emissionId: params.emissionId, title }}, attributes);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
getPodcastMakerUrl,
|
|
74
|
+
getSharePath,
|
|
75
|
+
getSmartLink
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import RadiolineIcon from "../../icons/RadiolineIcon.vue";
|
|
2
|
+
import TuninIcon from "../../icons/TuninIcon.vue";
|
|
3
|
+
import PodcastAddictIcon from "../../icons/PodcastAddictIcon.vue";
|
|
4
|
+
import PocketCastIcon from "../../icons/PocketCastIcon.vue";
|
|
5
|
+
import PlayerFmIcon from "../../icons/PlayerFmIcon.vue";
|
|
6
|
+
import IHeartIcon from "../../icons/IHeartIcon.vue";
|
|
7
|
+
import AmazonMusicIcon from "../../icons/AmazonMusicIcon.vue";
|
|
8
|
+
import DeezerIcon from "../../icons/DeezerIcon.vue";
|
|
9
|
+
import ApplePodcastIcon from "../../icons/ApplePodcastIcon.vue";
|
|
10
|
+
import CastboxIcon from "../../icons/CastboxIcon.vue";
|
|
11
|
+
import PodcastRepublicIcon from "../../icons/PodcastRepublicIcon.vue";
|
|
12
|
+
import PodbeanIcon from "../../icons/PodbeanIcon.vue";
|
|
13
|
+
import YoutubeIcon from "vue-material-design-icons/Youtube.vue";
|
|
14
|
+
import SpotifyIcon from "vue-material-design-icons/Spotify.vue";
|
|
15
|
+
import { Annotations } from "@/stores/class/general";
|
|
16
|
+
import { computed, type Component } from "vue";
|
|
17
|
+
import { useI18n } from "vue-i18n";
|
|
18
|
+
|
|
19
|
+
export interface SharePlatform {
|
|
20
|
+
/** ID of the platform */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Display text for the platform */
|
|
23
|
+
label: string;
|
|
24
|
+
/** Icon of the platform */
|
|
25
|
+
icon: Component;
|
|
26
|
+
/** Title for buttons */
|
|
27
|
+
title: string;
|
|
28
|
+
/** Color of the icon */
|
|
29
|
+
color: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SharePlatformUrl extends SharePlatform {
|
|
33
|
+
/** URL to the platform */
|
|
34
|
+
url: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const useSharePlatforms = () => {
|
|
38
|
+
const { t } = useI18n();
|
|
39
|
+
|
|
40
|
+
const platforms = computed((): Array<SharePlatform> => {
|
|
41
|
+
return [{
|
|
42
|
+
name: "applePodcast",
|
|
43
|
+
label: t("Apple podcast"),
|
|
44
|
+
icon: ApplePodcastIcon,
|
|
45
|
+
title: "Apple Podcast | iTunes",
|
|
46
|
+
color:"#aa1dd3"
|
|
47
|
+
}, {
|
|
48
|
+
name: "deezer",
|
|
49
|
+
label: t("Deezer"),
|
|
50
|
+
icon: DeezerIcon,
|
|
51
|
+
title: "Deezer",
|
|
52
|
+
color:"#a238ff",
|
|
53
|
+
}, {
|
|
54
|
+
name: "spotify",
|
|
55
|
+
label: t("Spotify"),
|
|
56
|
+
icon: SpotifyIcon,
|
|
57
|
+
title: "Spotify",
|
|
58
|
+
color: "#1ed760",
|
|
59
|
+
}, {
|
|
60
|
+
name: "amazon",
|
|
61
|
+
label: t("Amazon"),
|
|
62
|
+
icon: AmazonMusicIcon,
|
|
63
|
+
title: "Amazon Music",
|
|
64
|
+
color: "#0c6cb3",
|
|
65
|
+
}, {
|
|
66
|
+
name: "iHeart",
|
|
67
|
+
label: t("iHeart"),
|
|
68
|
+
icon: IHeartIcon,
|
|
69
|
+
title: "iHeart",
|
|
70
|
+
color:"#e11b22"
|
|
71
|
+
}, {
|
|
72
|
+
name: "playerFm",
|
|
73
|
+
label: t("Player FM"),
|
|
74
|
+
icon: PlayerFmIcon,
|
|
75
|
+
title: "PlayerFM",
|
|
76
|
+
color:"#bb202a"
|
|
77
|
+
}, {
|
|
78
|
+
name: "pocketCasts",
|
|
79
|
+
label: t("Pocket Casts"),
|
|
80
|
+
icon: PocketCastIcon,
|
|
81
|
+
title: "Pocket Casts",
|
|
82
|
+
color:"#f43e37"
|
|
83
|
+
}, {
|
|
84
|
+
name: "podcastAddict",
|
|
85
|
+
label: t("Podcast Addict"),
|
|
86
|
+
icon: PodcastAddictIcon,
|
|
87
|
+
title: "Podcast Addict",
|
|
88
|
+
color:"#f4842d"
|
|
89
|
+
}, {
|
|
90
|
+
name: "radioline",
|
|
91
|
+
label: t("Radioline"),
|
|
92
|
+
icon: RadiolineIcon,
|
|
93
|
+
title: "Radioline",
|
|
94
|
+
color:"#1678bd"
|
|
95
|
+
}, {
|
|
96
|
+
name: "tunein",
|
|
97
|
+
label: t("Tunein"),
|
|
98
|
+
icon: TuninIcon,
|
|
99
|
+
title: "TuneIn",
|
|
100
|
+
color:"#36b4a7"
|
|
101
|
+
}, {
|
|
102
|
+
name: "youtube",
|
|
103
|
+
label: t("YouTube Music"),
|
|
104
|
+
icon: YoutubeIcon,
|
|
105
|
+
title: "YouTube Music",
|
|
106
|
+
color: "#fe0000",
|
|
107
|
+
}, {
|
|
108
|
+
name: "castbox",
|
|
109
|
+
label: t("Castbox"),
|
|
110
|
+
icon: CastboxIcon,
|
|
111
|
+
title: "Castbox",
|
|
112
|
+
color: "#fe6222",
|
|
113
|
+
}, {
|
|
114
|
+
name: "podbean",
|
|
115
|
+
label: t("PodBean"),
|
|
116
|
+
icon: PodbeanIcon,
|
|
117
|
+
title: "PodBean",
|
|
118
|
+
color: "#428200",
|
|
119
|
+
}, {
|
|
120
|
+
name: "podcastrepublic",
|
|
121
|
+
label: t("Podcast Republic"),
|
|
122
|
+
icon: PodcastRepublicIcon,
|
|
123
|
+
title: "Podcast Republic",
|
|
124
|
+
color: "#5c85dd",
|
|
125
|
+
}];
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get platforms with their associated links.
|
|
130
|
+
* A platform with no link will not be included
|
|
131
|
+
* @param annotations The annotations of the element for which to get the
|
|
132
|
+
platforms links
|
|
133
|
+
*/
|
|
134
|
+
function getPlatformsWithLinks(annotations:Annotations|undefined): Array<SharePlatformUrl> {
|
|
135
|
+
const ary: Array<SharePlatformUrl> = [];
|
|
136
|
+
platforms.value.forEach(p => {
|
|
137
|
+
const url = getUrl(p.name, annotations);
|
|
138
|
+
if (url) {
|
|
139
|
+
ary.push({
|
|
140
|
+
...p,
|
|
141
|
+
url
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
return ary;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getUrl(sub: string, annotations: Annotations|undefined): string | undefined {
|
|
149
|
+
return externaliseLinks(
|
|
150
|
+
annotations?.[sub] as string | undefined,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function externaliseLinks(link?: string): string | undefined {
|
|
155
|
+
if (!link) {
|
|
156
|
+
return link;
|
|
157
|
+
}
|
|
158
|
+
link = link.trim();
|
|
159
|
+
return !link.startsWith("http") && !link.startsWith("//")
|
|
160
|
+
? "//" + link
|
|
161
|
+
: link;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
getPlatformsWithLinks,
|
|
166
|
+
platforms
|
|
167
|
+
}
|
|
168
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useGeneralStore } from "../../stores/GeneralStore";
|
|
2
2
|
import { useRoute } from "vue-router";
|
|
3
3
|
import { useI18n } from "vue-i18n";
|
|
4
|
+
|
|
4
5
|
export const useMetaTitle = ()=>{
|
|
5
6
|
|
|
6
7
|
const route = useRoute();
|
|
@@ -8,12 +9,12 @@ export const useMetaTitle = ()=>{
|
|
|
8
9
|
const {t} = useI18n();
|
|
9
10
|
|
|
10
11
|
function updateMetaTitle(){
|
|
11
|
-
if(""!==route.meta.title){
|
|
12
|
-
document.title = route.meta.title ? t(route.meta.title) +' - '+ generalStore.metaTitle: generalStore.metaTitle;
|
|
12
|
+
if("" !== route.meta.title){
|
|
13
|
+
document.title = route.meta.title ? t(route.meta.title as string) + ' - ' + generalStore.metaTitle: generalStore.metaTitle;
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
return {
|
|
17
18
|
updateMetaTitle
|
|
18
|
-
|
|
19
|
+
}
|
|
19
20
|
}
|
|
@@ -186,6 +186,7 @@ async function fetchContent(reset: boolean): Promise<void> {
|
|
|
186
186
|
first: reset? 0: dfirst.value,
|
|
187
187
|
size: dsize.value,
|
|
188
188
|
query: props.query,
|
|
189
|
+
groupId: props.emissionGroups?.map(g => g.groupId),
|
|
189
190
|
organisationId: organisation.value,
|
|
190
191
|
monetisable: props.monetisable,
|
|
191
192
|
iabId: props.iabId,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
<script setup lang="ts">
|
|
15
15
|
import { Emission } from "@/stores/class/general/emission";
|
|
16
|
-
import PresentationItem from "../../
|
|
16
|
+
import PresentationItem from "../../layouts/PresentationItem.vue";
|
|
17
17
|
import { computed } from "vue";
|
|
18
18
|
import { RouteLocationRaw } from "vue-router";
|
|
19
19
|
|
|
@@ -31,7 +31,7 @@ import { AxiosError } from "axios";
|
|
|
31
31
|
import {useResizePhone} from "../../composable/useResizePhone";
|
|
32
32
|
import { ListClassicReturn } from "@/stores/class/general/listReturn";
|
|
33
33
|
|
|
34
|
-
import PresentationLayout from "../../
|
|
34
|
+
import PresentationLayout from "../../layouts/PresentationLayout.vue";
|
|
35
35
|
|
|
36
36
|
const EmissionItemPresentation = defineAsyncComponent(
|
|
37
37
|
() => import("./EmissionPresentationItem.vue"),
|