@saooti/octopus-sdk 41.3.0 → 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 CHANGED
@@ -1,5 +1,26 @@
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
+
3
24
  ## 42.3.0 (09/01/2026)
4
25
 
5
26
  **Features**
@@ -27,7 +48,7 @@
27
48
 
28
49
  **Fixes**
29
50
 
30
- - Recherche avancées
51
+ - Recherche avancée
31
52
  - Correction chargements groupes & ayants-droits depuis routing
32
53
  - Filtrage des groupes par organisation
33
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "41.3.0",
3
+ "version": "41.3.1",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -44,7 +44,7 @@ export interface PodcastSearchOptions extends Paginable<PodcastSort> {
44
44
  /** Filter by emission ID */
45
45
  emissionId?: number|number[];
46
46
  /** Filter by emission groups */
47
- emissionGroups?: EmissionGroup[];
47
+ groupId?: number[];
48
48
  /** Filter by organisation ID */
49
49
  organisationId?: string[];
50
50
  /** Filter by title containing */
@@ -134,12 +134,6 @@ function processSearchParameters(search: PodcastSearchOptions): FetchParam {
134
134
  parameters.after = value;
135
135
  } else if (key === 'pageSize') {
136
136
  parameters.size = value;
137
- } else if (key === 'emissionGroups') {
138
- const emissionIds = [search.emissionId ?? undefined].flat();
139
- search.emissionGroups.forEach(group => {
140
- emissionIds.push(...group.emissionIds);
141
- });
142
- parameters.emissionId = emissionIds;
143
137
  } else {
144
138
  parameters[key] = value;
145
139
  }
@@ -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,
@@ -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"
@@ -23,7 +23,7 @@
23
23
  ]"
24
24
  >
25
25
  <!-- eslint-disable vue/no-v-html -->
26
- <div ref="descriptionPodcast" v-html="description" />
26
+ <div ref="descriptionPodcast" v-html="displayHelper.urlify(description)" />
27
27
  <!-- eslint-enable -->
28
28
  </div>
29
29
  <div
@@ -46,6 +46,8 @@ import { Podcast } from "../../../stores/class/general/podcast";
46
46
  import { computed, nextTick, onBeforeMount, ref, Ref, useTemplateRef } from "vue";
47
47
  import { Conference } from "@/stores/class/conference/conference";
48
48
 
49
+ import displayHelper from "../../../helper/displayHelper";
50
+
49
51
  //Props
50
52
  const props = defineProps<{
51
53
  podcast: Podcast;
@@ -201,7 +201,7 @@ async function fetchContent(reset: boolean): Promise<void> {
201
201
  pageSize: dsize.value,
202
202
  organisationId: organisation.value,
203
203
  emissionId: props.emissionId,
204
- emissionGroups: props.emissionGroups,
204
+ groupId: props.emissionGroups?.map(g => g.groupId),
205
205
  iabId: props.iabId,
206
206
  participantId: props.participantId,
207
207
  query: props.query,
@@ -3,6 +3,7 @@
3
3
  <legend class="h4 mb-2 mt-3">
4
4
  {{ t("player parameters") }}
5
5
  </legend>
6
+
6
7
  <template v-if="choseNumberEpisode">
7
8
  <div v-if="displayChoiceAllEpisodes" role="radiogroup">
8
9
  <div
@@ -37,6 +38,15 @@
37
38
  </div>
38
39
  <ChooseEpisodesNumber v-else :episodes-number="episodesNumber" @update-number="emit('update:episodesNumber', $event)"/>
39
40
  </template>
41
+
42
+ <ClassicCheckbox
43
+ v-if="displayAutoHeight"
44
+ :text-init="autoHeight"
45
+ id-checkbox="auto-height-checkbox"
46
+ :label="t('Miniplayer - Parameters - Auto height')"
47
+ @update:text-init="emit('update:autoHeight', $event)"
48
+ />
49
+
40
50
  <ClassicCheckbox
41
51
  v-if="displayIsVisible"
42
52
  :text-init="isVisible"
@@ -105,6 +115,8 @@ const props = defineProps({
105
115
  isPodcastNotVisible: { default: false, type: Boolean },
106
116
  episodesNumber: { default: 3, type: Number },
107
117
  insertCode: { default: false, type: Boolean },
118
+ displayAutoHeight: { default: false, type: Boolean },
119
+ autoHeight: { default: false, type: Boolean },
108
120
  })
109
121
 
110
122
 
@@ -117,7 +129,9 @@ const emit = defineEmits([
117
129
  "update:displayTranscript",
118
130
  "update:displayWave",
119
131
  "update:playerAutoPlay",
120
- "update:insertCode"]);
132
+ "update:insertCode",
133
+ "update:autoHeight"
134
+ ]);
121
135
 
122
136
 
123
137
  //Data
@@ -136,4 +150,4 @@ watch(episodeChoiceDisplay, async () => {
136
150
  emit("episodeChoiceDisplay", episodeChoiceDisplay.value);
137
151
  });
138
152
 
139
- </script>
153
+ </script>
@@ -48,6 +48,7 @@
48
48
  v-model:player-auto-play="playerAutoPlay"
49
49
  v-model:episodes-number="episodesNumber"
50
50
  v-model:insert-code="insertCode"
51
+ v-model:auto-height="autoHeight"
51
52
  :display-is-visible="displayIsVisible"
52
53
  :is-podcast-not-visible="isPodcastNotVisible"
53
54
  :chose-number-episode="displayChoiceAllEpisodes"
@@ -56,6 +57,7 @@
56
57
  :display-article-param="displayArticleParam"
57
58
  :display-wave-param="displayWaveParam"
58
59
  :display-insert-code="displayInsertCode"
60
+ :display-auto-height="iFrameModel.toLowerCase().includes('large')"
59
61
  @episode-choice-display="episodeChoiceDisplay = $event"
60
62
  />
61
63
 
@@ -132,6 +134,7 @@ const displayTranscript = ref(true);
132
134
  const displayWave = ref(false);
133
135
  const playerAutoPlay = ref(false);
134
136
  const insertCode = ref(false);
137
+ const autoHeight = ref(false);
135
138
  const orgaAttributes : Ref<{ [key: string]: string | number | boolean | undefined }| undefined>= ref(undefined);
136
139
 
137
140
 
@@ -244,6 +247,7 @@ const iFrameSrc = computed(() => {
244
247
  url.push(`${props.podcast.podcastId}`);
245
248
  }
246
249
  }
250
+
247
251
  return addUrlParameters(url).join("");
248
252
  });
249
253
  const iFrameHeight = computed(() => {
@@ -357,6 +361,9 @@ function addUrlParameters(url: Array<string>) {
357
361
  if (insertCode.value) {
358
362
  url.push("&insertCode=true");
359
363
  }
364
+ if (autoHeight.value) {
365
+ url.push("&autoHeight=true");
366
+ }
360
367
  return url;
361
368
  }
362
369
  function initColor(): void {
@@ -7,7 +7,7 @@
7
7
  >
8
8
  <template #body>
9
9
  <!-- eslint-disable vue/no-v-html -->
10
- <div v-if="!save && !error" class="content" v-html="message" />
10
+ <div v-if="!save && !error" class="content" v-html="displayHelper.urlify(message)" />
11
11
  <!-- eslint-enable -->
12
12
  <ClassicLoading
13
13
  v-if="save || error"
@@ -45,6 +45,7 @@ import ClassicModal from "../modal/ClassicModal.vue";
45
45
  import ClassicLoading from "../../form/ClassicLoading.vue";
46
46
  import { onMounted, ref, useTemplateRef } from "vue";
47
47
  import { useI18n } from "vue-i18n";
48
+ import displayHelper from "../../../helper/displayHelper";
48
49
  //Props
49
50
  const props = defineProps({
50
51
  title: { default: undefined, type: String },
@@ -119,7 +119,6 @@
119
119
  </template>
120
120
 
121
121
  <script setup lang="ts">
122
- import classicApi from "../../api/classicApi";
123
122
  import { state } from "../../stores/ParamSdkStore";
124
123
  import displayHelper from "../../helper/displayHelper";
125
124
  import {useImageProxy} from "../composable/useImageProxy";
@@ -140,6 +139,7 @@ import { useSimplePageParam } from "../composable/route/useSimplePageParam";
140
139
 
141
140
  import ErrorMessage from "../misc/ErrorMessage.vue";
142
141
  import ClassicHelpButton from "../misc/ClassicHelpButton.vue";
142
+ import { emissionApi } from "../../api/emissionApi";
143
143
 
144
144
  const ShareAnonymous = defineAsyncComponent(() => import("../display/sharing/ShareAnonymous.vue"));
145
145
  const PodcastFilterList = defineAsyncComponent(
@@ -243,10 +243,8 @@ async function getEmissionDetails(): Promise<void> {
243
243
  loaded.value = false;
244
244
  error.value = false;
245
245
  try {
246
- emission.value = await classicApi.fetchData<Emission>({
247
- api: 0,
248
- path: "emission/" + props.emissionId,
249
- });
246
+ emission.value = await emissionApi.get(props.emissionId);
247
+ filterStore.updateOrgaIfNecessary(emission.value.orga.id);
250
248
  if (
251
249
  "PUBLIC" !== emission.value.orga.privacy &&
252
250
  filterStore.filterOrgaId !== emission.value.orga.id &&
@@ -12,7 +12,6 @@
12
12
  >
13
13
  <section class="module-box">
14
14
  <EditBox v-if="editRight && !isPodcastmaker" :playlist="playlist" />
15
- <router-link :to="{ name: 'playlist-smartlink', params: { playlistId: playlist.playlistId } }">SmartLink</router-link>
16
15
  <div class="mb-5 mt-3 description-text">
17
16
  <img
18
17
  v-lazy="useProxyImageUrl(playlist.imageUrl, '250')"
@@ -66,7 +65,6 @@ import {useOrgaComputed} from "../composable/useOrgaComputed";
66
65
  import {useSeoTitleUrl} from "../composable/route/useSeoTitleUrl";
67
66
  import ClassicLoading from "../form/ClassicLoading.vue";
68
67
  import PodcastList from "../display/playlist/PodcastList.vue";
69
- import classicApi from "../../api/classicApi";
70
68
  import { useFilterStore } from "../../stores/FilterStore";
71
69
  import { state } from "../../stores/ParamSdkStore";
72
70
  import displayHelper from "../../helper/displayHelper";
@@ -78,6 +76,7 @@ import { AxiosError } from "axios";
78
76
  import { useI18n } from "vue-i18n";
79
77
  import { useRoute } from "vue-router";
80
78
  import { useSimplePageParam } from "../composable/route/useSimplePageParam";
79
+ import { playlistApi } from "../../api/playlistApi";
81
80
  const ShareSocialsButtons = defineAsyncComponent(
82
81
  () => import("../display/sharing/ShareSocialsButtons.vue"),
83
82
  );
@@ -164,10 +163,8 @@ async function getPlaylistDetails(): Promise<void> {
164
163
  try {
165
164
  loaded.value = false;
166
165
  error.value = false;
167
- playlist.value = await classicApi.fetchData<Playlist>({
168
- api: 0,
169
- path: "playlist/" + props.playlistId,
170
- });
166
+ playlist.value = await playlistApi.get(props.playlistId);
167
+ filterStore.updateOrgaIfNecessary(playlist.value.organisation?.id);
171
168
  if (
172
169
  (!editRight.value && playlistRadio.value) ||
173
170
  ("PUBLIC" !== playlist.value.organisation?.privacy &&
@@ -96,6 +96,8 @@ import { AxiosError } from "axios";
96
96
  import { useCommentStore } from "../../stores/CommentStore";
97
97
  import { useI18n } from "vue-i18n";
98
98
  import { useRoute } from "vue-router";
99
+ import { podcastApi } from "../../api/podcastApi";
100
+
99
101
  const ShareSocialsButtons = defineAsyncComponent(
100
102
  () => import("../display/sharing/ShareSocialsButtons.vue"),
101
103
  );
@@ -268,6 +270,7 @@ async function fetchConferenceStatus() {
268
270
  }
269
271
  function updatePodcast(podcastUpdated: Podcast): void {
270
272
  podcast.value = podcastUpdated;
273
+ filterStore.updateOrgaIfNecessary(podcastUpdated.organisation.id);
271
274
  }
272
275
  function initError(): void {
273
276
  error.value = true;
@@ -290,7 +293,7 @@ async function getPodcastDetails(): Promise<void> {
290
293
  initError();
291
294
  return;
292
295
  }
293
- podcast.value = data;
296
+ updatePodcast(data);
294
297
  generalStore.contentToDisplayUpdate(data);
295
298
  if (
296
299
  (!podcast.value.availability.visibility ||
@@ -323,10 +326,8 @@ function podcastInProcessing(){
323
326
  return;
324
327
  }
325
328
  infoReload.value = setTimeout(async () => {
326
- podcast.value = await classicApi.fetchData<Podcast>({
327
- api: 0,
328
- path: "podcast/" + props.podcastId,
329
- });
329
+ const podcast = await podcastApi.get(props.podcastId);
330
+ updatePodcast(podcast);
330
331
  podcastInProcessing();
331
332
  }, 2000);
332
333
  }
@@ -4,7 +4,7 @@
4
4
  </div>
5
5
  <article v-if="element">
6
6
  <!-- Top part of smartlink, with image, title and description -->
7
- <div class="d-flex">
7
+ <div class="content">
8
8
  <img
9
9
  v-lazy="useProxyImageUrl(element.imageUrl, '250')"
10
10
  width="250"
@@ -13,21 +13,17 @@
13
13
  aria-hidden="true"
14
14
  alt=""
15
15
  >
16
- <div class="content">
17
- <h1>{{ title }}</h1>
18
- <p>
19
- {{ element.description }}
20
- </p>
21
-
22
- <button
23
- v-if="latestPodcast"
24
- class="btn btn-primary pe-3"
25
- @click="playLatestPodcast"
26
- >
27
- <PlayIcon class="me-2" />
28
- {{ $t('SmartLink - Listen to latest episode') }}
29
- </button>
30
- </div>
16
+ <h1>{{ title }}</h1>
17
+ <div v-html="displayHelper.urlify(element.description)" />
18
+
19
+ <button
20
+ v-if="latestPodcast"
21
+ class="btn btn-primary pe-3"
22
+ @click="playLatestPodcast"
23
+ >
24
+ <PlayIcon class="me-2" />
25
+ {{ $t('SmartLink - Listen to latest episode') }}
26
+ </button>
31
27
  </div>
32
28
 
33
29
  <!-- Links -->
@@ -87,6 +83,7 @@ import { Podcast } from '@/stores/class/general/podcast';
87
83
  import { usePlayerStore } from '../../stores/PlayerStore';
88
84
  import { podcastApi, PodcastSort } from '../../api/podcastApi';
89
85
  import { useSharePath } from '../composable/share/useSharePath';
86
+ import displayHelper from '../../helper/displayHelper';
90
87
 
91
88
  const { updatePathParams } = useSeoTitleUrl();
92
89
  const { useProxyImageUrl } = useImageProxy();
@@ -282,9 +279,12 @@ article {
282
279
  .img-box {
283
280
  // Move image
284
281
  margin-top: -70px;
285
- margin-left: 20px;
286
282
  margin-bottom: 0px;
287
283
 
284
+ // Move image left with content going to its right, and below it
285
+ float: left;
286
+ margin-right: 20px;
287
+
288
288
  // Add a border
289
289
  border: 2px solid var(--background);
290
290
  border-radius: var(--border-radius);
@@ -1,8 +1,23 @@
1
+ const REGEX_URL = /[^">](https?:\/\/[^\s<]+)/g;
2
+ const REGEX_A = /<a (.+?)>/g;
3
+
1
4
  export default {
2
5
  urlify(text: string|undefined): string {
3
- const urlRegex = /[^">](https?:\/\/[^\s<]+)/g;
4
- if (!text) return "";
5
- return text.replace(urlRegex, (url: string) => {
6
+ if (!text) {
7
+ return "";
8
+ }
9
+
10
+ // Add target="_blank" to <a>
11
+ const withTarget = text.replace(REGEX_A, (full: string, content: string) => {
12
+ if (full.includes('target="_blank"')) {
13
+ return full;
14
+ } else {
15
+ return '<a ' + content + ' target="_blank">';
16
+ }
17
+ });
18
+
19
+ // Convert URL to <a>
20
+ return withTarget.replace(REGEX_URL, (url: string) => {
6
21
  return (
7
22
  '<a href="' + url + '" target="_blank" rel="noreferrer noopener" :title="t(\'New window\', {text: '+url+'})">' + url + "</a>"
8
23
  );
@@ -12,4 +27,4 @@ export default {
12
27
  await navigator.clipboard.writeText(link);
13
28
  return callback();
14
29
  },
15
- };
30
+ };
@@ -425,5 +425,6 @@
425
425
  "SmartLink - Listen to latest episode": "Hören Sie sich die neueste Folge an",
426
426
  "SmartLink - Made by": "Gehostet von",
427
427
  "SmartLink - To emission on podcastmaker": "Offizielle Seite der Show – {organisation}",
428
- "SmartLink - To playlist on podcastmaker": "Offizielle Playlist-Seite – {organisation}"
428
+ "SmartLink - To playlist on podcastmaker": "Offizielle Playlist-Seite – {organisation}",
429
+ "Miniplayer - Parameters - Auto height": "Automatische Höhe"
429
430
  }
@@ -425,5 +425,6 @@
425
425
  "SmartLink - Listen to latest episode": "Listen to the latest episode",
426
426
  "SmartLink - Made by": "Hosted by",
427
427
  "SmartLink - To emission on podcastmaker": "Official page of the show - {organisation}",
428
- "SmartLink - To playlist on podcastmaker": "Official playlist page - {organisation}"
428
+ "SmartLink - To playlist on podcastmaker": "Official playlist page - {organisation}",
429
+ "Miniplayer - Parameters - Auto height": "Auto Height"
429
430
  }
@@ -425,5 +425,6 @@
425
425
  "SmartLink - Listen to latest episode": "Escuche el último episodio",
426
426
  "SmartLink - Made by": "Anfitrión:",
427
427
  "SmartLink - To emission on podcastmaker": "Página oficial del espectáculo - {organisation}",
428
- "SmartLink - To playlist on podcastmaker": "Página oficial de la lista de reproducción: {organisation}"
428
+ "SmartLink - To playlist on podcastmaker": "Página oficial de la lista de reproducción: {organisation}",
429
+ "Miniplayer - Parameters - Auto height": "Altura automática"
429
430
  }
@@ -448,5 +448,6 @@
448
448
  "SmartLink - Listen to latest episode": "Écouter le dernier épisode",
449
449
  "SmartLink - Made by": "Hébergé par",
450
450
  "SmartLink - To emission on podcastmaker": "Page officielle de l'émission - {organisation}",
451
- "SmartLink - To playlist on podcastmaker": "Page officielle de la playlist - {organisation}"
451
+ "SmartLink - To playlist on podcastmaker": "Page officielle de la playlist - {organisation}",
452
+ "Miniplayer - Parameters - Auto height": "Hauteur automatique"
452
453
  }
@@ -426,5 +426,6 @@
426
426
  "SmartLink - Listen to latest episode": "Ascolta l'ultimo episodio",
427
427
  "SmartLink - Made by": "Ospitato da",
428
428
  "SmartLink - To emission on podcastmaker": "Pagina ufficiale dello spettacolo - {organisation}",
429
- "SmartLink - To playlist on podcastmaker": "Pagina della playlist ufficiale - {organisation}"
429
+ "SmartLink - To playlist on podcastmaker": "Pagina della playlist ufficiale - {organisation}",
430
+ "Miniplayer - Parameters - Auto height": "Altezza automatica"
430
431
  }
@@ -424,5 +424,6 @@
424
424
  "SmartLink - Listen to latest episode": "Poslušajte najnovejšo epizodo",
425
425
  "SmartLink - Made by": "Gostuje",
426
426
  "SmartLink - To emission on podcastmaker": "Uradna stran razstave - {organisation}",
427
- "SmartLink - To playlist on podcastmaker": "Uradna stran seznama predvajanja - {organisation}"
427
+ "SmartLink - To playlist on podcastmaker": "Uradna stran seznama predvajanja - {organisation}",
428
+ "Miniplayer - Parameters - Auto height": "Samodejna višina"
428
429
  }
@@ -32,17 +32,31 @@ export const useFilterStore = defineStore("FilterStore", () => {
32
32
  * ID of the current organisation.
33
33
  */
34
34
  const filterOrgaId = computed((): string|undefined => {
35
+ // When explicitly asking to view all content, disable filter
35
36
  if (route?.query.viewall === "true") {
36
37
  return undefined;
38
+
39
+ // When productor set in route, use it
37
40
  } else if (route?.query.productor) {
38
41
  return route.query.productor as string;
42
+
43
+ // If we don´t have an ID stored, use data from auth store
39
44
  } else if(_filterOrgaId.value === null) {
40
45
  return authStore.authOrgaId;
46
+
47
+ // Otherwise use stored ID
41
48
  } else {
42
49
  return _filterOrgaId.value ?? undefined;
43
50
  }
44
51
  });
45
52
 
53
+ /** Update stored organisation ID only if not already set */
54
+ function updateOrgaIfNecessary(orgaId: string|undefined): void {
55
+ if (orgaId && filterOrgaId.value === undefined) {
56
+ _filterOrgaId.value = orgaId;
57
+ }
58
+ }
59
+
46
60
  /**
47
61
  * The ID of the current organisation, regardless of other options.
48
62
  * Use this if you want to know the organisation of the user even in
@@ -105,6 +119,8 @@ export const useFilterStore = defineStore("FilterStore", () => {
105
119
  filterOrgaId,
106
120
  realOrgaId,
107
121
 
122
+ updateOrgaIfNecessary,
123
+
108
124
  filterUpdateOrga,
109
125
  filterUpdateIab,
110
126
  filterUpdateRubrique,
@@ -125,4 +141,4 @@ export const useFilterStore = defineStore("FilterStore", () => {
125
141
  });
126
142
 
127
143
  /** Type for the FilterStore */
128
- export type FilterStore = ReturnType<typeof useFilterStore>;
144
+ export type FilterStore = ReturnType<typeof useFilterStore>;