@saooti/octopus-sdk 41.12.1-beta → 41.12.1-beta3

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
@@ -2,14 +2,26 @@
2
2
 
3
3
  ## 41.12.1 (En cours)
4
4
 
5
+ **Features**
6
+
7
+ - Ajout de nouvelles fonctions dans l'api `rubriqueApi`
8
+ - Option de `AdvancedSearch` pour ne pas afficher le filtre de groupe
9
+ - Ajout callback sur fermeture de notifications
10
+
5
11
  **Fixes**
6
12
 
7
13
  - Correction d'une anomalie de chargement du style de la modale utilisée par
8
14
  `ClassicNotifications`
15
+ - Ajout mode alternatif pour la récupération des épisodes dans
16
+ `PodcastPresentationList`
17
+ - Affichage des infos additionelles dans PresentationItem même sans description
9
18
 
10
19
  **Misc**
11
20
 
12
21
  - Suppression de la marge entre le titre et le contenu pour `PresentationLayout`
22
+ - Uniformisation espacement pour composants PM
23
+ - Mise à jour règles eslint
24
+ - Ajustement couleurs pour boutons secondaires
13
25
 
14
26
  ## 41.12.0 (13/07/2026)
15
27
 
package/eslint-config.mjs CHANGED
@@ -45,6 +45,9 @@ export default typescriptEslint.config(
45
45
  // Number of attributes per line (increase because sometimes two is not a lot)
46
46
  "vue/max-attributes-per-line": ['warn', { singleline: 2 } ],
47
47
 
48
+ // Disable default values required for props
49
+ "vue/require-default-prop": ['off'],
50
+
48
51
  "@typescript-eslint/no-unused-vars": "warn"
49
52
  }
50
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "41.12.1-beta",
3
+ "version": "41.12.1-beta3",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -1,6 +1,5 @@
1
1
  import { Podcast, PodcastProcessingStatus, PodcastType, SimplifiedPodcast } from '../stores/class/general/podcast';
2
2
  import { ListClassicReturn } from '../stores/class/general/listReturn';
3
- import { useAuthStore } from '../stores/AuthStore';
4
3
  import classicApi from './classicApi';
5
4
  import { ModuleApi } from './apiConnection';
6
5
  import { unique } from '../helper/arrayHelper';
@@ -24,6 +24,18 @@ async function searchRubriquages(organisationIds: Array<string>, searchOptions?:
24
24
  });
25
25
  }
26
26
 
27
+ /**
28
+ * Fetch rubriquage data by ID
29
+ * @param rubriquageId ID of the rubriquage to fetch
30
+ * @returns The rubriquage
31
+ */
32
+ async function getRubriquage(rubriquageId: number): Promise<Rubriquage> {
33
+ return classicApi.fetchData<Rubriquage>({
34
+ api: ModuleApi.DEFAULT,
35
+ path: `rubriquage/${rubriquageId}`
36
+ });
37
+ }
38
+
27
39
  async function searchRubriques(searchOptions?: {
28
40
  rubriquageId?: number;
29
41
  organisationId?: string|Array<string>;
@@ -61,6 +73,7 @@ async function getCachedRubrique(rubriqueId: number): Promise<Rubrique> {
61
73
  }
62
74
 
63
75
  export const rubriquesApi = {
76
+ getRubriquage,
64
77
  getRubrique,
65
78
  getCachedRubrique,
66
79
  searchRubriquages,
@@ -1,18 +1,21 @@
1
1
  <template>
2
2
  <div class="d-flex flex-column list-episode">
3
+ <h2 v-if="title" class="mb-0">
4
+ {{ title }}
5
+ </h2>
3
6
  <ClassicLoading
4
7
  :loading-text="loading ? t('Loading emissions ...') : undefined"
5
8
  />
6
9
  <SwiperList
7
10
  v-if="(displayRubriquage && rubriques) || !(displayRubriquage && loaded)"
8
- :size-item-overload="itemSize"
11
+ :size-item-overload="sizeItemOverload"
9
12
  :list-object="allEmissions"
10
13
  >
11
14
  <template #octopusSlide="{ option }">
12
15
  <EmissionPresentationItem
13
16
  v-if="emissionDisplay === 'simple'"
14
17
  :emission="option"
15
- class="mx-2 inline-list-element"
18
+ class="inline-list-element"
16
19
  is-description
17
20
  :is-vertical="emissionVertical"
18
21
  />
@@ -76,6 +79,8 @@ const props = defineProps<{
76
79
  rubriqueId?: number;
77
80
  /** Filter on rubriquage */
78
81
  rubriquageId?: number;
82
+ /** Title of the section */
83
+ title?: string;
79
84
  }>();
80
85
 
81
86
 
@@ -93,6 +98,16 @@ const {handle403} = useErrorHandler();
93
98
  //Computed
94
99
  const displayRubriquage = computed(() => state.emissionsPage.rubriquage);
95
100
 
101
+ const sizeItemOverload = computed(() => {
102
+ if (props.itemSize) {
103
+ return props.itemSize;
104
+ } else if (props.emissionDisplay === 'simple') {
105
+ return 25;
106
+ } else {
107
+ return undefined;
108
+ }
109
+ })
110
+
96
111
  onMounted(()=>{
97
112
  fetchNext();
98
113
  if (displayRubriquage.value) {
@@ -181,6 +181,8 @@ const props = withDefaults(defineProps<{
181
181
  rubriqueFilter?: Array<RubriquageFilter>;
182
182
  /** The filter on groups */
183
183
  emissionGroups?: Array<EmissionGroup>;
184
+ /** Force hide the emission group filter */
185
+ hideEmissionGroups?: boolean;
184
186
  }>(), {
185
187
  sort: "DATE",
186
188
  monetisable: "UNDEFINED",
@@ -217,6 +219,10 @@ const filterStore = useFilterStore();
217
219
  const authStore = useAuthStore();
218
220
 
219
221
  onMounted(async() => {
222
+ if (props.hideEmissionGroups) {
223
+ showEmissionGroups.value = false;
224
+ return;
225
+ }
220
226
  // Only show emission groups if there are some
221
227
  const nbGroups = await groupsApi.count({
222
228
  organisationIds: [props.organisationId]
@@ -7,7 +7,7 @@
7
7
  <button
8
8
  v-for="paginateButton in buttonsLeft"
9
9
  :key="paginateButton.title"
10
- class="btn"
10
+ class="btn btn-paginate"
11
11
  :title="paginateButton.title"
12
12
  :disabled="paginateButton.disabled"
13
13
  @click="paginateButton.action"
@@ -23,10 +23,10 @@
23
23
  </svg>
24
24
  </button>
25
25
  <template v-for="pageNumber in pagination" :key="pageNumber">
26
- <span v-if="null === pageNumber" class="btn btn-min-width"> ... </span>
26
+ <span v-if="null === pageNumber" class="btn btn-min-width btn-paginate"> ... </span>
27
27
  <button
28
28
  v-else
29
- class="btn btn-min-width"
29
+ class="btn btn-min-width btn-paginate"
30
30
  :class="{ active: page === pageNumber - 1 }"
31
31
  @click="changeFirst((pageNumber - 1) * rowsPerPage)"
32
32
  >
@@ -36,7 +36,7 @@
36
36
  <button
37
37
  v-for="paginateButton in buttonsRight"
38
38
  :key="paginateButton.title"
39
- class="btn btn-min-width"
39
+ class="btn btn-min-width btn-paginate"
40
40
  :title="paginateButton.title"
41
41
  :disabled="paginateButton.disabled"
42
42
  @click="paginateButton.action"
@@ -176,6 +176,7 @@ function changeFirst(newFirst: number) {
176
176
  emit("update:first", newFirst);
177
177
  }
178
178
  </script>
179
+
179
180
  <style lang="scss">
180
181
 
181
182
  .octopus-app {
@@ -189,15 +190,18 @@ function changeFirst(newFirst: number) {
189
190
  padding: 0.5rem 0;
190
191
  z-index: 10;
191
192
 
192
- .btn {
193
+ .btn.btn-paginate {
193
194
  border-radius: 0;
195
+ color: var(--octopus-btn-paginate-fg);
196
+ background: var(--octopus-btn-paginate-bg);
194
197
 
195
198
  &.active {
196
- background: var(--octopus-primary-more-transparent);
199
+ color: var(--octopus-btn-paginate-active-fg);
200
+ background: var(--octopus-btn-paginate-active-bg);
197
201
  }
198
202
  }
199
203
  }
200
-
204
+
201
205
  .module-box .paginate-fixed,
202
206
  .octopus-modal .paginate-fixed,
203
207
  .octopus-accordion .paginate-fixed {
@@ -4,7 +4,7 @@
4
4
  <swiper
5
5
  :key="manualReload"
6
6
  :slides-per-view="numberItem"
7
- :space-between="0"
7
+ :space-between="gapPx"
8
8
  :loop="loop"
9
9
  :slides-offset-before="offsetSwiper"
10
10
  :slides-offset-after="offsetSwiper"
@@ -47,7 +47,8 @@ const props = defineProps({
47
47
  sizeItemOverload: { default: undefined, type: Number },
48
48
  })
49
49
 
50
- //Data
50
+ //Data
51
+ const gapPx = 10;
51
52
  const manualReload = ref(0);
52
53
  const numberItem = ref(5);
53
54
  const offsetSwiper = ref(0);
@@ -73,7 +74,10 @@ const sizeItem = computed(() => {
73
74
  ? state.generalParameters.podcastItem
74
75
  : 13.5;
75
76
  });
76
- const itemRecalculizedSize = computed(() => widthSwiperUsable.value / numberItem.value);
77
+ const itemRecalculizedSize = computed(() => {
78
+ const totalGap = gapPx * Math.max(numberItem.value - 1, 0);
79
+ return (widthSwiperUsable.value - totalGap) / numberItem.value;
80
+ });
77
81
 
78
82
  /** Indicates that the swiper should loop */
79
83
  const loop = computed((): boolean => {
@@ -119,10 +123,10 @@ function onWindowResize(){
119
123
  const el = rootRef?.value as HTMLElement;
120
124
  if (!el) return;
121
125
  widthSwiperUsable.value =el.offsetWidth - offsetSwiper.value * 2;
122
- const sixteen = domHelper.convertRemToPixels(sizeItem.value + 0.5);
126
+ const itemSizePx = domHelper.convertRemToPixels(sizeItem.value + 0.5);
123
127
  numberItem.value = Math.max(
124
128
  1,
125
- Math.floor(widthSwiperUsable.value / sixteen),
129
+ Math.floor((widthSwiperUsable.value + gapPx) / (itemSizePx + gapPx)),
126
130
  );
127
131
  itemSizeWithoutRecalculed.value =el.offsetWidth / numberItem.value;
128
132
  }
@@ -153,7 +157,7 @@ function slideChange() {
153
157
  );
154
158
  wrapper.style.transform =
155
159
  "translate3d(" +
156
- (nbTransformItems * itemRecalculizedSize.value + offsetSwiper.value) +
160
+ (nbTransformItems * (itemRecalculizedSize.value + gapPx) + offsetSwiper.value) +
157
161
  "px, 0px, 0px)";
158
162
  }
159
163
  </script>
@@ -81,6 +81,7 @@ const props = withDefaults(defineProps<{
81
81
  before?: string;
82
82
  after?: string;
83
83
  includeHidden?: boolean;
84
+ /** Enable the display of number of items in ListPaginate */
84
85
  showCount?: boolean;
85
86
  displaySortText?: boolean;
86
87
  /** Criteria to sort on */
@@ -57,14 +57,34 @@ import { podcastApi, PodcastSort } from "../../../api/podcastApi";
57
57
  import { usePresentationItem } from "../../composable/usePresentationItem";
58
58
 
59
59
  //Props
60
- const props = defineProps({
61
- organisationId: { default: undefined, type: String },
62
- title: { default: "", type: String },
63
- href: { default: undefined, type: String },
64
- buttonText: { default: undefined, type: String },
65
- isDescription: { default: false, type: Boolean },
66
- rubriquesId: { default: [], type: Array<number> },
67
- })
60
+ const props = withDefaults(defineProps<{
61
+ /**
62
+ * ID of the organisation
63
+ */
64
+ organisationId?: string;
65
+ /**
66
+ * Title of the section
67
+ */
68
+ title?: string;
69
+ /**
70
+ * Link to the "more" section
71
+ */
72
+ href?: string;
73
+ /**
74
+ * Label for the "more" button
75
+ */
76
+ buttonText?: string;
77
+ /**
78
+ * Display podcasts exclusively from these rubriques
79
+ */
80
+ rubriquesId?: Array<number>;
81
+ /**
82
+ * Mode of retrieval for podcasts
83
+ */
84
+ retrievalMode?: 'by-emission'|'any';
85
+ }>(), {
86
+ retrievalMode: 'by-emission'
87
+ });
68
88
 
69
89
  //Data
70
90
  const loading = ref(true);
@@ -92,47 +112,16 @@ watch(podcasts, async () => {
92
112
  async function fetchNext(): Promise<void> {
93
113
  loading.value = true;
94
114
  try {
95
- // Retrieve latest emissions
96
- const emissions = await classicApi.fetchData<ListClassicReturn<Emission>>({
97
- api: 0,
98
- path: "emission/search",
99
- parameters: {
100
- first: 0,
101
- size: 5,
102
- organisationId: props.organisationId,
103
- sort: "LAST_PODCAST_DESC",
104
- rubriqueId: props.rubriquesId
105
- },
106
- specialTreatement: true,
107
- });
108
-
109
- const promises: Array<Promise<SimplifiedPodcast>> = [];
110
-
111
- for (let i = 0; i < emissions.result.length; i++) {
112
- promises.push(podcastApi.search({
113
- first: 0,
114
- size: 1,
115
- organisationId: [props.organisationId],
116
- emissionId: [emissions.result[i].emissionId],
117
- sort: PodcastSort.DATE,
118
- rubriqueId: props.rubriquesId
119
- }).then(r => r.result[0]));
115
+ let func: () => Promise<Array<Podcast>>;
116
+ if (props.retrievalMode === 'any') {
117
+ func = fetchPodcasts;
118
+ } else {
119
+ func = fetchPodcastsByEmission;
120
120
  }
121
-
122
- // Retrieve the podcasts for these emissions
123
- const data = await Promise.all(promises);
124
-
125
- podcasts.value = podcasts.value.concat(
126
- data.filter((em: SimplifiedPodcast | null) => null !== em && undefined !== em).map(p => {
127
- // Get emission from podcast
128
- const emission = emissions.result.find(e => e.emissionId === p.emissionId);
129
- // Create full podcast from simplified + emission
130
- return simplifiedToFull(p, emission.orga, emission);
131
- })
132
- );
121
+ const result = await func();
133
122
 
134
123
  // Sort podcasts by pub date so that the most recent one is focused
135
- podcasts.value.sort((p1, p2) => {
124
+ podcasts.value = result.sort((p1, p2) => {
136
125
  return new Date(p2.pubDate).getTime() - new Date(p1.pubDate).getTime();
137
126
  });
138
127
 
@@ -145,6 +134,57 @@ async function fetchNext(): Promise<void> {
145
134
  loading.value = false;
146
135
  }
147
136
 
137
+ async function fetchPodcasts(): Promise<Array<Podcast>> {
138
+ const response = await podcastApi.searchFull({
139
+ first: 0,
140
+ size: 5,
141
+ organisationId: [props.organisationId],
142
+ sort: PodcastSort.DATE,
143
+ rubriqueId: props.rubriquesId
144
+ }, true);
145
+
146
+ return response.result;
147
+ }
148
+
149
+ async function fetchPodcastsByEmission(): Promise<Array<Podcast>> {
150
+ // Retrieve latest emissions
151
+ const emissions = await classicApi.fetchData<ListClassicReturn<Emission>>({
152
+ api: 0,
153
+ path: "emission/search",
154
+ parameters: {
155
+ first: 0,
156
+ size: 5,
157
+ organisationId: props.organisationId,
158
+ sort: "LAST_PODCAST_DESC",
159
+ rubriqueId: props.rubriquesId
160
+ },
161
+ specialTreatement: true
162
+ });
163
+
164
+ const promises: Array<Promise<SimplifiedPodcast>> = [];
165
+
166
+ for (let i = 0; i < emissions.result.length; i++) {
167
+ promises.push(podcastApi.search({
168
+ first: 0,
169
+ size: 1,
170
+ organisationId: [props.organisationId],
171
+ emissionId: [emissions.result[i].emissionId],
172
+ sort: PodcastSort.DATE,
173
+ rubriqueId: props.rubriquesId
174
+ }, true).then(r => r.result[0]));
175
+ }
176
+
177
+ // Retrieve the podcasts for these emissions
178
+ const data = await Promise.all(promises);
179
+
180
+ return data.filter((em: SimplifiedPodcast | null) => null !== em && undefined !== em).map(p => {
181
+ // Get emission from podcast
182
+ const emission = emissions.result.find(e => e.emissionId === p.emissionId);
183
+ // Create full podcast from simplified + emission
184
+ return simplifiedToFull(p, emission.orga, emission);
185
+ });
186
+ }
187
+
148
188
  function route(podcast: Podcast): RouteLocationRaw {
149
189
  return {
150
190
  name: 'podcast',
@@ -215,7 +215,6 @@ defineExpose({ reset });
215
215
  position: relative;
216
216
  border-radius: var(--octopus-border-radius);
217
217
  overflow: hidden;
218
- background: var(--octopus-secondary);
219
218
  transition: all 0.2s linear 0s;
220
219
 
221
220
  &.open{
@@ -44,7 +44,7 @@
44
44
  {{ name }}
45
45
  </div>
46
46
  <div
47
- v-if="!isPhone && description"
47
+ v-if="!isPhone && (description || additionalInfo)"
48
48
  ref="descriptionItemContainer"
49
49
  class="element-description htms-wysiwyg-content mt-0"
50
50
  >
@@ -59,6 +59,7 @@
59
59
  </div>
60
60
  <!-- eslint-disable vue/no-v-html -->
61
61
  <div
62
+ v-if="description"
62
63
  ref="descriptionItem"
63
64
  v-html="urlify(description || '')"
64
65
  />
@@ -2,7 +2,7 @@
2
2
  A simple layout to display 5 elements over 3 columns
3
3
  -->
4
4
  <template>
5
- <div class="d-flex flex-column p-3">
5
+ <div class="d-flex flex-column py-3">
6
6
  <h2 v-if="title">
7
7
  {{ title }}
8
8
  </h2>
@@ -98,10 +98,10 @@ defineProps<{
98
98
 
99
99
  .column {
100
100
  flex-shrink: 0;
101
- width: calc((100% - 420px) / 2);
101
+ width: calc((100% - 420px - 1rem) / 2);
102
102
 
103
103
  @media (width <= 1550px) {
104
- width: calc((100% - 420px));
104
+ width: calc((100% - 420px - 1rem));
105
105
  }
106
106
 
107
107
  @media (width <= 960px) {
@@ -15,6 +15,8 @@ interface Notification {
15
15
  type: NotificationType;
16
16
  /** When set to false, disallow manual closing of modal notification (default: true) */
17
17
  closeable?: boolean;
18
+ /** Callback called when the notification is closed */
19
+ closeCallback?: () => void;
18
20
  }
19
21
 
20
22
  export const useNotificationStore = defineStore('notifications', () => {
@@ -34,6 +36,10 @@ export const useNotificationStore = defineStore('notifications', () => {
34
36
  if (notif !== null) {
35
37
  const idx = notificationQueue.value.indexOf(notif);
36
38
  notificationQueue.value.splice(idx, 1);
39
+
40
+ if (notif.closeCallback) {
41
+ notif.closeCallback();
42
+ }
37
43
  }
38
44
  }
39
45
 
@@ -46,11 +46,17 @@
46
46
  // Buttons
47
47
  --octopus-btn-primary-bg: var(--octopus-primary);
48
48
  --octopus-btn-primary-fg: var(--octopus-color-on-primary);
49
+ --octopus-btn-secondary-bg: var(--octopus-secondary);
50
+ --octopus-btn-secondary-fg: black;
49
51
  --octopus-btn-play-bg: var(--octopus-primary-less-transparent);
50
52
  --octopus-btn-play-fg: white;
51
53
  --octopus-btn-play-radius: var(--octopus-border-radius);
52
54
  --octopus-btn-social-bg: var(--octopus-secondary);
53
55
  --octopus-btn-social-fg: var(--octopus-primary);
56
+ --octopus-btn-paginate-fg: var(--octopus-btn-secondary-fg);
57
+ --octopus-btn-paginate-bg: var(--octopus-btn-secondary-bg);
58
+ --octopus-btn-paginate-active-fg: var(--octopus-btn-paginate-fg);
59
+ --octopus-btn-paginate-active-bg: var(--octopus-primary-more-transparent);
54
60
 
55
61
  // Player
56
62
  // Color for the transcript background
@@ -88,7 +88,8 @@ input:not([class^="vs__"]), button:not([class^="vs__"]), select:not([class^="vs_
88
88
  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
89
89
  font-size:.7rem;
90
90
  border-radius: var(--octopus-border-radius);
91
- background: var(--octopus-secondary);
91
+ color: var(--octopus-btn-secondary-fg);
92
+ background: var(--octopus-btn-secondary-bg);
92
93
  text-decoration: none !important;
93
94
  white-space: nowrap;
94
95
  border-width: 0;
@@ -28,9 +28,9 @@ describe('PresentationItem', () => {
28
28
  expect(wrapper.find('.text-secondary').exists()).toBe(false);
29
29
  });
30
30
 
31
- it('does not render additionalInfo when there is no description', async () => {
31
+ it('renders each additionalInfo even when there is no description', async () => {
32
32
  const wrapper = await mount({ additionalInfo: ['Saooti'] });
33
- expect(wrapper.find('.text-secondary').exists()).toBe(false);
33
+ expect(wrapper.find('.text-secondary').exists()).toBe(true);
34
34
  });
35
35
  });
36
36
  });