@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/doc/README.md +5 -0
  3. package/doc/routing.md +50 -0
  4. package/index.ts +8 -1
  5. package/package.json +6 -2
  6. package/src/App.vue +7 -36
  7. package/src/api/organisationApi.ts +15 -2
  8. package/src/api/podcastApi.ts +1 -8
  9. package/src/components/composable/player/usePlayerLogic.ts +2 -2
  10. package/src/components/composable/route/useSeoTitleUrl.ts +2 -1
  11. package/src/components/composable/share/useSharePath.ts +77 -0
  12. package/src/components/composable/share/useSharePlateforms.ts +168 -0
  13. package/src/components/composable/useImageProxy.ts +3 -3
  14. package/src/components/composable/useMetaTitle.ts +5 -4
  15. package/src/components/display/emission/EmissionList.vue +1 -0
  16. package/src/components/display/emission/EmissionPresentationItem.vue +1 -1
  17. package/src/components/display/emission/EmissionPresentationList.vue +1 -1
  18. package/src/components/display/filter/AdvancedSearch.vue +1 -1
  19. package/src/components/display/podcasts/PodcastItem.vue +3 -1
  20. package/src/components/display/podcasts/PodcastList.vue +1 -1
  21. package/src/components/display/podcasts/PodcastModuleBox.vue +1 -1
  22. package/src/components/display/podcasts/PodcastPresentationList.vue +2 -2
  23. package/src/components/display/sharing/PlayerParameters.vue +16 -2
  24. package/src/components/display/sharing/ShareNewsletter.vue +6 -5
  25. package/src/components/display/sharing/SharePlayer.vue +7 -0
  26. package/src/components/display/sharing/SubscribeButtons.vue +19 -117
  27. package/src/components/form/ClassicInputText.vue +14 -4
  28. package/src/components/icons/CastboxIcon.vue +20 -0
  29. package/src/components/icons/PodbeanIcon.vue +27 -0
  30. package/src/components/icons/PodcastRepublicIcon.vue +63 -0
  31. package/src/components/misc/TopBar.vue +7 -3
  32. package/src/components/misc/modal/MessageModal.vue +2 -1
  33. package/src/components/pages/EmissionPage.vue +4 -6
  34. package/src/components/pages/PlaylistPage.vue +3 -5
  35. package/src/components/pages/PodcastPage.vue +6 -5
  36. package/src/components/pages/SmartLinkPage.vue +381 -0
  37. package/src/helper/displayHelper.ts +19 -4
  38. package/src/layouts/FullLayout.vue +48 -0
  39. package/src/layouts/SimpleLayout.vue +25 -0
  40. package/src/locale/{de.ts → de.json} +199 -192
  41. package/src/locale/{en.ts → en.json} +187 -183
  42. package/src/locale/{es.ts → es.json} +200 -194
  43. package/src/locale/{fr.ts → fr.json} +96 -76
  44. package/src/locale/it.json +431 -0
  45. package/src/locale/messages.ts +9 -6
  46. package/src/locale/{sl.ts → sl.json} +190 -176
  47. package/src/router/router.ts +9 -1
  48. package/src/router/routes.ts +29 -10
  49. package/src/router/utils.ts +31 -1
  50. package/src/stores/FilterStore.ts +17 -1
  51. package/src/stores/PlayerStore.ts +14 -10
  52. package/src/stores/class/general/emission.ts +2 -1
  53. package/src/stores/class/general/index.ts +4 -0
  54. package/src/stores/class/general/organisation.ts +7 -1
  55. package/src/stores/class/general/playlist.ts +6 -0
  56. package/src/stores/class/general/podcast.ts +3 -0
  57. package/tests/components/pages/SmartLinkPage.spec.ts +90 -0
  58. package/tests/utils.ts +75 -0
  59. package/tsconfig.json +44 -40
  60. package/vitest.config.js +18 -0
  61. package/src/locale/it.ts +0 -420
  62. /package/src/components/{layout → layouts}/PresentationItem.vue +0 -0
  63. /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 avancées
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
@@ -0,0 +1,5 @@
1
+ # Documentation octopus-sdk
2
+
3
+ ## Table des matières
4
+
5
+ 1. [Routing](./routing.md)
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.2.1",
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
- "typescript": "^5.9.3"
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
- <template v-if="pageFullyLoad">
4
- <TopBar/>
5
- <main role="main">
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 { defineAsyncComponent, getCurrentInstance, onBeforeMount, ref, watch } from "vue";
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
- if (firstDisplayCategoryFilter.value) {
49
- return;
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
  };
@@ -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
- emissionGroups?: EmissionGroup[];
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
- return {
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
  }
@@ -25,6 +25,7 @@ export const useSeoTitleUrl = ()=>{
25
25
  }
26
26
 
27
27
  return {
28
- updatePathParams
28
+ updatePathParams,
29
+ stringUrlEncode
29
30
  }
30
31
  }
@@ -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
+ };
@@ -24,7 +24,7 @@ export const useImageProxy = ()=>{
24
24
  return url;
25
25
  }
26
26
 
27
- return {
27
+ return {
28
28
  useProxyImageUrl
29
- }
30
- }
29
+ }
30
+ }
@@ -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
- return {
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 "../../layout/PresentationItem.vue";
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 "../../layout/PresentationLayout.vue";
34
+ import PresentationLayout from "../../layouts/PresentationLayout.vue";
35
35
 
36
36
  const EmissionItemPresentation = defineAsyncComponent(
37
37
  () => import("./EmissionPresentationItem.vue"),
@@ -33,7 +33,7 @@
33
33
  />
34
34
 
35
35
  <!-- Group filters -->
36
- <div v-if="!isEmission && showEmissionGroups" class="mt-3 d-flex">
36
+ <div v-if="showEmissionGroups" class="mt-3 d-flex">
37
37
  <ClassicCheckbox
38
38
  v-model:text-init="emissionGroupCheckbox"
39
39
  class="flex-shrink-0"