@saooti/octopus-sdk 41.0.19 → 41.0.21

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,31 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 41.0.21 (25/11/2025)
4
+
5
+ **Fixes**
6
+
7
+ - Correction affichage durée quand il n'y a pas de décimales
8
+
9
+ **Misc**
10
+
11
+ - Amélioration affichage bouton PlayPodcast
12
+
13
+ ## 41.0.20 (21/11/2025)
14
+
15
+ **Features**
16
+
17
+ - `durationHelper.convertTimestamptoString` accepte les nanosecondes
18
+
19
+ **Fixes**
20
+
21
+ - Bannière 'en cours de traitement' ne s'affiche que pour la consultation d'épisode
22
+
23
+ **Misc**
24
+
25
+ - Ajout de slots sur `ClassicRadio`
26
+ - Correction d'un warning sur `SwiperList`
27
+ - Mise à jour des dépendances
28
+
3
29
  ## 41.0.19 (13/11/2025)
4
30
 
5
31
  **Features**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "41.0.19",
3
+ "version": "41.0.21",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -18,47 +18,47 @@
18
18
  "dependencies": {
19
19
  "@multiformats/murmur3": "^2.1.8",
20
20
  "@popperjs/core": "^2.11.8",
21
- "@stomp/stompjs": "^7.1.1",
22
- "@tato30/vue-pdf": "^1.11.3",
23
- "@tiptap/extension-hard-break": "^2.14.0",
24
- "@tiptap/extension-link": "^2.14.0",
25
- "@tiptap/extension-text-style": "^2.14.0",
26
- "@tiptap/extension-underline": "^2.14.0",
27
- "@tiptap/starter-kit": "^2.14.0",
28
- "@tiptap/vue-3": "^2.14.0",
29
- "@vuepic/vue-datepicker": "^11.0.2",
30
- "@vueuse/core": "^13.3.0",
31
- "autoprefixer": "^10.4.21",
32
- "axios": "^1.9.0",
33
- "dayjs": "^1.11.13",
34
- "emoji-mart-vue-fast": "^15.0.4",
21
+ "@stomp/stompjs": "^7.2.1",
22
+ "@tato30/vue-pdf": "^1.11.5",
23
+ "@tiptap/extension-hard-break": "^2.27.1",
24
+ "@tiptap/extension-link": "^2.27.1",
25
+ "@tiptap/extension-text-style": "^2.27.1",
26
+ "@tiptap/extension-underline": "^2.27.1",
27
+ "@tiptap/starter-kit": "^2.27.1",
28
+ "@tiptap/vue-3": "^2.27.1",
29
+ "@vuepic/vue-datepicker": "^11.0.3",
30
+ "@vueuse/core": "^13.9.0",
31
+ "autoprefixer": "^10.4.22",
32
+ "axios": "^1.13.2",
33
+ "dayjs": "^1.11.19",
34
+ "emoji-mart-vue-fast": "^15.0.5",
35
35
  "express": "^5.1.0",
36
- "globals": "^16.2.0",
37
- "hls.js": "^1.6.5",
38
- "humanize-duration": "^3.33.0",
39
- "pinia": "^3.0.3",
36
+ "globals": "^16.5.0",
37
+ "hls.js": "^1.6.14",
38
+ "humanize-duration": "^3.33.1",
39
+ "pinia": "^3.0.4",
40
40
  "postcss-html": "^1.8.0",
41
41
  "qrcode.vue": "^3.6.0",
42
- "sass": "^1.89.2",
42
+ "sass": "^1.94.1",
43
43
  "sockjs-client": "^1.6.1",
44
- "stylelint": "^16.20.0",
44
+ "stylelint": "^16.25.0",
45
45
  "stylelint-config-recommended-scss": "^15.0.1",
46
- "stylelint-config-recommended-vue": "^1.6.0",
46
+ "stylelint-config-recommended-vue": "^1.6.1",
47
47
  "stylelint-config-standard": "^38.0.0",
48
48
  "stylelint-config-standard-scss": "^15.0.1",
49
49
  "stylelint-config-standard-vue": "^1.0.0",
50
50
  "stylelint-gamut": "^1.3.4",
51
- "swiper": "^11.2.8",
52
- "typescript-eslint": "^8.34.0",
53
- "video.js": "^8.23.3",
51
+ "swiper": "^11.2.10",
52
+ "typescript-eslint": "^8.47.0",
53
+ "video.js": "^8.23.6",
54
54
  "videojs-quality-selector-hls": "^1.1.1",
55
- "vite": "^6.3.5",
55
+ "vite": "^6.4.1",
56
56
  "vite-bundle-visualizer": "^1.2.1",
57
- "vue": "^3.5.16",
58
- "vue-i18n": "^11.1.5",
57
+ "vue": "^3.5.24",
58
+ "vue-i18n": "^11.1.12",
59
59
  "vue-material-design-icons": "^5.3.1",
60
60
  "vue-recaptcha": "^2.0.3",
61
- "vue-router": "^4.5.1",
61
+ "vue-router": "^4.6.3",
62
62
  "vue-select": "^4.0.0-beta.6",
63
63
  "vue3-lazyload": "^0.3.8",
64
64
  "vue3-swatches": "^1.2.4"
@@ -67,9 +67,9 @@
67
67
  "@types/sockjs-client": "^1.5.4",
68
68
  "@types/webpack-env": "^1.18.8",
69
69
  "@vitejs/plugin-vue": "^5.2.4",
70
- "eslint": "^9.28.0",
70
+ "eslint": "^9.39.1",
71
71
  "eslint-plugin-vue": "^9.33.0",
72
- "typescript": "^5.8.3"
72
+ "typescript": "^5.9.3"
73
73
  },
74
74
  "postcss": {
75
75
  "plugins": {
@@ -5,9 +5,11 @@
5
5
  :key="manualReload"
6
6
  :slides-per-view="numberItem"
7
7
  :space-between="0"
8
- :loop="true"
8
+ :loop="loop"
9
9
  :slides-offset-before="offsetSwiper"
10
10
  :slides-offset-after="offsetSwiper"
11
+ :allow-slide-next="loop"
12
+ :allow-slide-prev="loop"
11
13
  :navigation="true"
12
14
  :modules="modules"
13
15
  @slides-updated="slidesUpdated"
@@ -44,7 +46,6 @@ const props = defineProps({
44
46
 
45
47
  //Data
46
48
  const manualReload = ref(0);
47
- const modules = ref([Navigation]);
48
49
  const numberItem = ref(5);
49
50
  const offsetSwiper = ref(40);
50
51
  const widthSwiperUsable = ref(0);
@@ -71,6 +72,18 @@ const sizeItem = computed(() => {
71
72
  });
72
73
  const itemRecalculizedSize = computed(() => widthSwiperUsable.value / numberItem.value);
73
74
 
75
+ /** Indicates that the swiper should loop */
76
+ const loop = computed((): boolean => {
77
+ return (props.listObject.length > numberItem.value);
78
+ });
79
+
80
+ const modules = computed(() => {
81
+ if (loop.value === true) {
82
+ return [Navigation];
83
+ } else {
84
+ return [];
85
+ }
86
+ });
74
87
 
75
88
  //Watch
76
89
  watch(windowWidth, () => onWindowResize());
@@ -47,7 +47,6 @@
47
47
  <PodcastItem
48
48
  v-if="0 !== p.podcastId"
49
49
  :podcast="p"
50
- :in-list="true"
51
50
  />
52
51
 
53
52
  <template #preview>
@@ -47,7 +47,7 @@
47
47
  :podcast="podcast"
48
48
  :hide-play="hidePlay"
49
49
  :fetch-conference="fetchConference"
50
- :in-list="inList"
50
+ :show-processing="isAuthenticated"
51
51
  />
52
52
  <button
53
53
  v-if="displayDescription && isMobile"
@@ -69,6 +69,7 @@ import { Conference } from "@/stores/class/conference/conference";
69
69
  import {useImageProxy} from "../../composable/useImageProxy";
70
70
  import { computed, onBeforeMount, ref, watch } from "vue";
71
71
  import { useI18n } from "vue-i18n";
72
+ import { useAuthStore } from "../../../stores/AuthStore";
72
73
 
73
74
  //Props
74
75
  const props = defineProps({
@@ -77,9 +78,7 @@ const props = defineProps({
77
78
  displayDescription: { default: false, type: Boolean },
78
79
  arrowDirection: { default: "up", type: String },
79
80
  isAnimatorLive: { default: false, type: Boolean },
80
- fetchConference: { default: undefined, type: Object as () => Conference },
81
- /** Indicates that the podcast is displayed in a list */
82
- inList: { default: false, type: Boolean }
81
+ fetchConference: { default: undefined, type: Object as () => Conference }
83
82
  })
84
83
 
85
84
  //Emits
@@ -92,6 +91,7 @@ const isMobile = ref(false);
92
91
  //Composables
93
92
  const { t } = useI18n();
94
93
  const { useProxyImageUrl } = useImageProxy();
94
+ const { isAuthenticated } = useAuthStore();
95
95
 
96
96
  //Computed
97
97
  const mainRubrique = computed(() => {
@@ -10,7 +10,6 @@
10
10
  :display-description="0 !== description.length"
11
11
  :arrow-direction="arrowDirection"
12
12
  :fetch-conference="fetchConference"
13
- :in-list="inList"
14
13
  @hide-description="hideDescription"
15
14
  @show-description="showDescription"
16
15
  />
@@ -50,9 +49,7 @@ import { Conference } from "@/stores/class/conference/conference";
50
49
  //Props
51
50
  const props = defineProps({
52
51
  podcast: { default: () => ({}), type: Object as () => Podcast },
53
- fetchConference: { default: undefined, type: Object as () => Conference },
54
- /** Indicates that the podcast is displayed in a list */
55
- inList: { default: false, type: Boolean }
52
+ fetchConference: { default: undefined, type: Object as () => Conference }
56
53
  });
57
54
 
58
55
  //Data
@@ -35,7 +35,6 @@
35
35
  <PodcastItem
36
36
  v-if="0 !== p.podcastId"
37
37
  :podcast="p"
38
- in-list
39
38
  />
40
39
  <template #preview>
41
40
  <router-link
@@ -25,6 +25,7 @@
25
25
  : '',
26
26
  ]"
27
27
  class="me-3"
28
+ show-processing
28
29
  :hide-play="isLiveReadyToRecord"
29
30
  :podcast="podcast"
30
31
  :playing-podcast="playingPodcast"
@@ -33,7 +33,7 @@
33
33
  <PodcastIsPlaying v-if="playingPodcast && !playerStore.playerVideo" />
34
34
  <time
35
35
  v-if="!isVideoPodcast"
36
- class="ms-1"
36
+ class="ms-1 me-2"
37
37
  :datetime="durationIso"
38
38
  >
39
39
  {{ durationString }}
@@ -53,7 +53,7 @@
53
53
  />
54
54
  <PodcastIsPlaying v-if="playingPodcast && playerStore.playerVideo" />
55
55
  <time
56
- class="ms-2"
56
+ class="ms-2 me-2"
57
57
  :datetime="durationIso"
58
58
  >
59
59
  {{ durationString }}
@@ -93,7 +93,6 @@ import DurationHelper from "../../../helper/durationHelper";
93
93
  import { state } from "../../../stores/ParamSdkStore";
94
94
  import { Podcast } from "@/stores/class/general/podcast";
95
95
  import { Conference } from "@/stores/class/conference/conference";
96
- import { useAuthStore } from "../../../stores/AuthStore";
97
96
  import { usePlayerStore } from "../../../stores/PlayerStore";
98
97
  import { computed, defineAsyncComponent, ref } from "vue";
99
98
  import dayjs from "dayjs";
@@ -110,8 +109,8 @@ const props = defineProps({
110
109
  hidePlay: { default: false, type: Boolean },
111
110
  fetchConference: { default: undefined, type: Object as () => Conference },
112
111
  justButtons: { default: false, type: Boolean },
113
- /** Indicates that the podcast is displayed in a list */
114
- inList: { default: false, type: Boolean }
112
+ /** Indicates that the processing status of the episode may be shown */
113
+ showProcessing: { default: false, type: Boolean }
115
114
  })
116
115
 
117
116
 
@@ -120,7 +119,6 @@ const hoverType = ref("");
120
119
 
121
120
  //Composables
122
121
  const { t } = useI18n();
123
- const authStore = useAuthStore();
124
122
  const playerStore = usePlayerStore();
125
123
  const router = useRouter();
126
124
 
@@ -174,7 +172,7 @@ const classicPodcastPlay = computed(() => {
174
172
  });
175
173
 
176
174
  const displayBanner = computed(() => {
177
- return !classicPodcastPlay.value || ("PROCESSING" === props.podcast.processingStatus && !props.inList);
175
+ return !classicPodcastPlay.value || ("PROCESSING" === props.podcast.processingStatus && props.showProcessing);
178
176
  });
179
177
 
180
178
  const iconName = computed(() => {
@@ -151,6 +151,7 @@ const props = defineProps({
151
151
  errorText: { default: "", type: String },
152
152
  isTextarea: { default: false, type: Boolean },
153
153
  isWysiwyg: { default: false, type: Boolean },
154
+ /** A regex checked against the input, if the input doesn't match mark it with error */
154
155
  regex: { default: undefined, type: RegExp },
155
156
  canBeNull: { default: false, type: Boolean },
156
157
  inputMaxLengthField: { default: undefined, type: Number },
@@ -1,3 +1,12 @@
1
+ <!--
2
+ Simple component to display radio buttons.
3
+
4
+ Available slots:
5
+ `label-{option.value}`: Slot to replace the label of the current option.
6
+ Binding: `option`: the current option
7
+ `after-{option.value}`: Slot after the radio button
8
+ Binding: `option`: the current option
9
+ -->
1
10
  <template>
2
11
  <div role="radiogroup" class="d-flex" :class="isColumn ? 'flex-column' : ''">
3
12
  <div
@@ -14,23 +23,30 @@
14
23
  :value="option.value"
15
24
  :disabled="isDisabled"
16
25
  @input="onChange($event.target.value)"
17
- />
18
- <label class="c-hand" :for="idRadio + option.value">{{
19
- option.title
20
- }}</label>
26
+ >
27
+ <label class="c-hand" :for="idRadio + option.value">
28
+ <slot :name="'label-' + option.value" v-bind="slotBindings(option)">{{ option.title }}</slot>
29
+ </label>
30
+
31
+ <slot :name="'after-' + option.value" v-bind="slotBindings(option)" />
21
32
  </div>
22
33
  </div>
23
34
  </template>
24
35
 
25
36
  <script setup lang="ts">
26
37
 
38
+ interface Option {
39
+ title: string;
40
+ value: string | undefined
41
+ };
42
+
27
43
  //Props
28
- defineProps({
44
+ const { textInit } = defineProps({
29
45
  idRadio: { default: "", type: String },
30
46
  isDisabled: { default: false, type: Boolean },
31
47
  options: {
32
48
  default: () => [],
33
- type: Array as () => Array<{ title: string; value: string | undefined }>,
49
+ type: Array as () => Array<Option>,
34
50
  },
35
51
  textInit: { default: undefined, type: String },
36
52
  isColumn: { default: true, type: Boolean },
@@ -44,4 +60,11 @@ function onChange(value:string){
44
60
  emit('update:textInit', value)
45
61
  }
46
62
 
63
+ function slotBindings(option: Option): { option: Option; selected: boolean } {
64
+ return {
65
+ option,
66
+ selected: textInit === option.value
67
+ }
68
+ }
69
+
47
70
  </script>
@@ -65,7 +65,7 @@ const popoverRef = useTemplateRef('popover');
65
65
 
66
66
 
67
67
  //Composables
68
- const router= useRouter();
68
+ const router = useRouter();
69
69
 
70
70
  //Computed
71
71
  const popoverId = computed(() => "popover" + props.target);
@@ -238,11 +238,6 @@ async function getEmissionDetails(): Promise<void> {
238
238
  loaded.value = false;
239
239
  error.value = false;
240
240
  try {
241
- const reponse = await classicApi.fetchData<Emission>({
242
- api: 0,
243
- path: "emission/" + props.emissionId,
244
- });
245
- console.log(reponse);
246
241
  emission.value = await classicApi.fetchData<Emission>({
247
242
  api: 0,
248
243
  path: "emission/" + props.emissionId,
@@ -1,32 +1,56 @@
1
- export default {
2
- convertTimestamptoSeconds(timestamp: string) {
3
- timestamp = "00:00:00".substring(0, 8 - timestamp.length) + timestamp;
4
- const [hours, minutes, seconds] = timestamp.split(":");
5
- return Number(hours) * 60 * 60 + Number(minutes) * 60 + Number(seconds);
6
- },
7
- formatToString(value: number) {
1
+
2
+ const TIMESTAMP_REGEX = /(?:(\d{1,2}):)?([0-5]?\d):([0-5]\d)(?:.(\d{3}))?$/;
3
+
4
+ /**
5
+ * Convert a timestamp to seconds
6
+ */
7
+ function convertTimestamptoSeconds(timestamp: string): number|null {
8
+ const matches = timestamp.match(TIMESTAMP_REGEX);
9
+ if (matches) {
10
+ const [, hours, minutes, seconds, nanoseconds] = matches;
11
+ return Number(hours ?? 0) * 60 * 60 + Number(minutes ?? 0) * 60 + Number(seconds ?? 0) + Number(nanoseconds ?? 0) / 1000;
12
+ } else {
13
+ return null;
14
+ }
15
+ }
16
+
17
+ function formatToString(value: number) {
8
18
  if (value < 10) {
9
- return "0" + value;
19
+ return "0" + value;
10
20
  }
11
21
  return value.toString();
12
- },
13
- formatDuration(
22
+ }
23
+
24
+ function formatDuration(
14
25
  totalSeconds: number | null,
15
26
  separator = ":",
16
27
  isLast = false,
17
- ): string {
28
+ ): string {
18
29
  if (null === totalSeconds) {
19
- return "00:00";
30
+ return "00:00";
20
31
  }
21
32
  const hours = Math.floor(totalSeconds / 3600);
22
33
  const minutes = Math.floor((totalSeconds - hours * 3600) / 60);
23
34
  const seconds = totalSeconds - hours * 3600 - minutes * 60;
35
+
36
+ // In case seconds have decimal, round to 3 decimals, otherwise do not use decimals
37
+ const secondsStr = Math.round(seconds) == seconds ?
38
+ this.formatToString(seconds) :
39
+ this.formatToString(seconds.toFixed(3));
40
+
24
41
  return (
25
- (hours > 0 ? this.formatToString(hours) + separator : "") +
26
- this.formatToString(minutes) +
27
- separator +
28
- this.formatToString(seconds) +
29
- (isLast ? separator : "")
42
+ (hours > 0 ? this.formatToString(hours) + separator : "") +
43
+ this.formatToString(minutes) +
44
+ separator +
45
+ secondsStr +
46
+ (isLast ? separator : "")
30
47
  );
31
- },
32
- };
48
+ }
49
+
50
+ export default {
51
+ TIMESTAMP_REGEX,
52
+
53
+ convertTimestamptoSeconds,
54
+ formatDuration,
55
+ formatToString
56
+ }
@@ -50,6 +50,10 @@ export const useAuthStore = defineStore("AuthStore", {
50
50
  authVideoConfig: { active: false },
51
51
  }),
52
52
  getters: {
53
+ /** Indicates that the user is authenticated */
54
+ isAuthenticated(): boolean {
55
+ return this.authParam.accessToken !== undefined;
56
+ },
53
57
  isRoleAdmin(): boolean {
54
58
  return this.authRole.includes("ADMIN");
55
59
  },
@@ -62,26 +62,30 @@ main, #app{
62
62
 
63
63
  /** Flex style */
64
64
  .d-flex-low-importance{
65
- display: flex;
66
- }
65
+ display: flex;
66
+ }
67
67
 
68
68
  .d-flex-column{
69
- display: flex;
70
- flex-direction: row;
69
+ display: flex;
70
+ flex-direction: row;
71
71
 
72
- @media (width <= 960px) {
73
- flex-direction: column;
74
- }
75
- }
72
+ @media (width <= 960px) {
73
+ flex-direction: column;
74
+ }
75
+ }
76
76
 
77
77
  .d-flex-row{
78
- display: flex;
79
- flex-direction: column;
78
+ display: flex;
79
+ flex-direction: column;
80
80
 
81
- @media (width <= 960px) {
82
- flex-flow: row wrap;
83
- }
84
- }
81
+ @media (width <= 960px) {
82
+ flex-flow: row wrap;
83
+ }
84
+ }
85
+
86
+ .justify-space-between {
87
+ justify-content: space-between;
88
+ }
85
89
 
86
90
 
87
91
  @media (width <= 960px) {