@saooti/octopus-sdk 41.7.2 → 41.8.0

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 (36) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/CHANGELOG.md +28 -0
  3. package/index.ts +6 -2
  4. package/package.json +1 -1
  5. package/src/api/emissionApi.ts +44 -1
  6. package/src/api/podcastApi.ts +27 -5
  7. package/src/components/composable/route/useAdvancedParamInit.ts +4 -3
  8. package/src/components/composable/useRights.ts +196 -0
  9. package/src/components/composable/useSeasonsManagement.ts +43 -0
  10. package/src/components/display/live/RadioPlanning.vue +6 -4
  11. package/src/components/display/podcasts/PodcastFilterList.vue +100 -18
  12. package/src/components/display/podcasts/PodcastInlineListTemplate.vue +4 -1
  13. package/src/components/display/podcasts/PodcastList.vue +5 -1
  14. package/src/components/display/podcasts/PodcastModuleBox.vue +21 -4
  15. package/src/components/display/podcasts/PodcastSwiperList.vue +9 -3
  16. package/src/components/form/ClassicRadio.vue +13 -14
  17. package/src/components/misc/TopBar.vue +7 -1
  18. package/src/components/pages/EmissionPage.vue +22 -10
  19. package/src/locale/de.json +7 -1
  20. package/src/locale/en.json +7 -1
  21. package/src/locale/es.json +7 -1
  22. package/src/locale/fr.json +7 -1
  23. package/src/locale/it.json +7 -1
  24. package/src/locale/sl.json +7 -1
  25. package/src/stores/class/general/emission.ts +20 -0
  26. package/src/stores/class/general/podcast.ts +17 -0
  27. package/src/style/general.scss +7 -0
  28. package/tests/api/podcastApi.spec.ts +43 -0
  29. package/tests/components/composable/useAdvancedParamInit.spec.ts +90 -0
  30. package/tests/components/composable/useRights.spec.ts +265 -0
  31. package/tests/components/composable/useSeasonsManagement.spec.ts +35 -0
  32. package/tests/components/display/podcasts/PodcastFilterList.spec.ts +33 -0
  33. package/tests/components/display/podcasts/PodcastInlineListTemplate.spec.ts +23 -0
  34. package/tests/components/display/podcasts/PodcastModuleBox.spec.ts +49 -22
  35. package/tests/components/pages/EmissionPage.spec.ts +86 -0
  36. package/tests/utils.ts +12 -1
@@ -18,7 +18,9 @@
18
18
  :label="t('Search')"
19
19
  />
20
20
  </div>
21
+
21
22
  <PodcastList
23
+ v-if="!showSeasons"
22
24
  :first="dfirst"
23
25
  :size="dsize"
24
26
  :iab-id="iabId"
@@ -34,6 +36,36 @@
34
36
  :force-update-parameters="forceUpdateParameters"
35
37
  @fetch="fetch"
36
38
  />
39
+ <ClassicNav
40
+ v-else
41
+ v-model:active-tab="activeSeasonTab"
42
+ :tab-number="seasons.length"
43
+ >
44
+ <template v-for="season in seasons" #[tabNameSlot(season)]>
45
+ {{ $t('Podcast - Season N', { season }) }}
46
+ </template>
47
+
48
+ <template v-for="season in seasons" #[tabContentSlot(season)] :key="season">
49
+ <PodcastList
50
+ class="flex-grow-1"
51
+ :first="dfirst"
52
+ :size="dsize"
53
+ :iab-id="iabId"
54
+ :query="query"
55
+ :participant-id="participantId"
56
+ :emission-id="emissionId"
57
+ :organisation-id="productorId"
58
+ :sort-criteria="sort"
59
+ :reload="reloadList"
60
+ :include-hidden="editRight"
61
+ :show-count="showCount"
62
+ :display-sort-text="false"
63
+ :force-update-parameters="forceUpdateParameters"
64
+ :seasons="[season]"
65
+ @fetch="fetch($event, season)"
66
+ />
67
+ </template>
68
+ </ClassicNav>
37
69
  </section>
38
70
  </template>
39
71
 
@@ -44,28 +76,43 @@ import { Category } from "@/stores/class/general/category";
44
76
  import { defineAsyncComponent, ref, Ref, computed, watch } from "vue";
45
77
  import { Podcast } from "@/stores/class/general/podcast";
46
78
  import { useI18n } from "vue-i18n";
79
+ import ClassicNav from "../../misc/ClassicNav.vue";
80
+ import { Emission } from "@/stores/class/general/emission";
81
+ import { useSeasonsManagement } from "../../composable/useSeasonsManagement";
82
+ import { PodcastSort } from "../../../api/podcastApi";
47
83
  const CategoryChooser = defineAsyncComponent(
48
84
  () => import("../categories/CategoryChooser.vue"),
49
85
  );
50
86
 
51
87
  //Props
52
- const props = defineProps({
53
- first: { default: 0, type: Number },
54
- size: { default: 30, type: Number },
55
- query: { default: undefined, type: String },
56
- participantId: { default: undefined, type: Number },
57
- name: { default: undefined, type: String },
58
- emissionId: { default: undefined, type: Number },
59
- categoryFilter: { default: false, type: Boolean },
60
- reload: { default: false, type: Boolean },
61
- editRight: { default: false, type: Boolean },
62
- productorId: { default: () => [], type: Array as () => Array<string> },
63
- showCount: { default: false, type: Boolean },
64
- forceUpdateParameters: { default: false, type: Boolean },
65
- })
88
+ const props = withDefaults(defineProps<{
89
+ first?: number;
90
+ size?: number;
91
+ query?: string;
92
+ participantId?: number;
93
+ name?: string;
94
+ emissionId?: number;
95
+ categoryFilter?: boolean;
96
+ reload?: boolean;
97
+ editRight?: boolean;
98
+ productorId?: Array<string>;
99
+ showCount?: boolean;
100
+ forceUpdateParameters?: boolean;
101
+ /**
102
+ * Emission for which to display podcasts
103
+ * If set, will check for seasons
104
+ */
105
+ emission?: Emission;
106
+ }>(), {
107
+ first: 0,
108
+ size: 30
109
+ });
66
110
 
67
111
  //Emits
68
- const emit = defineEmits(["fetch", "update:query"]);
112
+ const emit = defineEmits<{
113
+ (e: "fetch", podcasts: Array<Podcast>, season: number|undefined): void;
114
+ (e: "update:query", query: string): void;
115
+ }>();
69
116
 
70
117
  //Data
71
118
  const dfirst = ref(props.first);
@@ -76,6 +123,7 @@ const iabId : Ref<number | undefined>= ref(undefined);
76
123
 
77
124
  //Composables
78
125
  const { t } = useI18n();
126
+ const { areSeasonsEnabled } = useSeasonsManagement();
79
127
 
80
128
  //Computed
81
129
  const titleFilter = computed(() => {
@@ -84,7 +132,32 @@ const titleFilter = computed(() => {
84
132
  : t("All podcast emission button");
85
133
  });
86
134
  const query = computed(() => searchPattern.value.length > 3 ? searchPattern.value : "");
87
- const sort = computed(() => !query.value.length ? "DATE" : "SCORE");
135
+
136
+ const showSeasons = computed(() => {
137
+ return props.emission !== undefined && areSeasonsEnabled(props.emission) && props.emission.seasonCount > 0;
138
+ });
139
+
140
+ const activeSeasonTab = ref(showSeasons.value ? (props.emission?.seasonCount ?? 1) - 1 : 0);
141
+
142
+ const sort = computed((): PodcastSort => {
143
+ if(showSeasons.value === true) {
144
+ return PodcastSort.SEASONAL;
145
+ } else if(!query.value.length) {
146
+ return PodcastSort.DATE;
147
+ } else {
148
+ return PodcastSort.SCORE;
149
+ }
150
+ });
151
+
152
+ const seasons = computed((): Array<number> => {
153
+ const ary: Array<number> = [];
154
+ if (showSeasons.value === true) {
155
+ for (let i = 1; i <= props.emission.seasonCount; i++) {
156
+ ary.push(i);
157
+ }
158
+ }
159
+ return ary;
160
+ });
88
161
 
89
162
  //Watch
90
163
  watch(()=>props.reload, () => {
@@ -98,7 +171,16 @@ watch(searchPattern, () => {
98
171
  function onCategorySelected(category: Category | undefined): void {
99
172
  iabId.value = category?.id ? category.id : undefined;
100
173
  }
101
- function fetch(podcasts: Array<Podcast>): void {
102
- emit("fetch", podcasts);
174
+ function fetch(podcasts: Array<Podcast>, season?: number): void {
175
+ emit("fetch", podcasts, season);
176
+ }
177
+
178
+ /** Name of the slot for the tab's title */
179
+ function tabNameSlot(season: number): string {
180
+ return `${season - 1}`;
181
+ }
182
+ /** Name of the slot for the tab's content */
183
+ function tabContentSlot(season: number): string {
184
+ return `tab${season - 1}`;
103
185
  }
104
186
  </script>
@@ -5,8 +5,9 @@
5
5
  {{ title }}
6
6
  </component>
7
7
  </div>
8
+
8
9
  <div v-if="!podcastId" class="d-flex justify-content-between">
9
- <div class="d-flex">
10
+ <div v-if="!noSort" class="d-flex">
10
11
  <button
11
12
  class="btn btn-underline"
12
13
  :class="{ active: !popularSort }"
@@ -82,6 +83,8 @@ const props = defineProps({
82
83
  noRubriquageId: { default: () => [], type: Array as () => Array<number> },
83
84
  podcastId: { default: undefined, type: Number },
84
85
  titleTag: { default: "h2", type: String },
86
+ /** Hide sort options */
87
+ noSort: { default: false, type: Boolean }
85
88
  })
86
89
 
87
90
  //Emits
@@ -83,6 +83,7 @@ const props = withDefaults(defineProps<{
83
83
  includeHidden?: boolean;
84
84
  showCount?: boolean;
85
85
  displaySortText?: boolean;
86
+ /** Criteria to sort on */
86
87
  sortCriteria?: PodcastSort;
87
88
  validity?: 'true'|'false'|''|boolean; // TODO improve this
88
89
  rubriqueId?: Array<number>;
@@ -96,6 +97,8 @@ const props = withDefaults(defineProps<{
96
97
  beneficiaries?: Array<string>;
97
98
  /** The emission groups to filter on */
98
99
  emissionGroups?: Array<EmissionGroup>;
100
+ /** The seasons to filter on */
101
+ seasons?: Array<number>;
99
102
  }>(), {
100
103
  first: 0,
101
104
  size: 30,
@@ -223,7 +226,8 @@ async function fetchContent(reset: boolean): Promise<void> {
223
226
  processingStatus: [PodcastProcessingStatus.Ready, PodcastProcessingStatus.Processing],
224
227
  withVideo: props.withVideo,
225
228
  tags: props.includeTag?.length ? props.includeTag : undefined,
226
- beneficiaries: props.beneficiaries ?? undefined
229
+ beneficiaries: props.beneficiaries ?? undefined,
230
+ season: props.seasons
227
231
  };
228
232
 
229
233
  try {
@@ -88,6 +88,12 @@
88
88
  {{ podcast.organisation.name }}
89
89
  </router-link>
90
90
  </div>
91
+ <div v-if="showSeasonNumber" class="mb-1">
92
+ {{ `${t('Podcast - Season')} : ${podcast.seasonNumber}` }}
93
+ </div>
94
+ <div v-if="showSeasonEpisodeNumber" class="mb-1">
95
+ {{ `${t('Podcast - Episode number')} : ${podcast.seasonEpisodeNumber}` }}
96
+ </div>
91
97
  <div v-if="'' !== photoCredit" class="mb-1">
92
98
  {{ t("Photo credits") + " : " + photoCredit }}
93
99
  </div>
@@ -161,8 +167,12 @@ import { state } from "../../../stores/ParamSdkStore";
161
167
  import { useAuthStore } from "../../../stores/AuthStore";
162
168
  import displayHelper from "../../../helper/displayHelper";
163
169
  import {usePodcastView} from "../../composable/podcasts/usePodcastView";
164
- import { Podcast } from "@/stores/class/general/podcast";
165
- import { Conference } from "@/stores/class/conference/conference";
170
+ import { Podcast } from "../../../stores/class/general/podcast";
171
+ import { Conference } from "../../../stores/class/conference/conference";
172
+ import { useI18n } from "vue-i18n";
173
+ import { useRouter } from "vue-router";
174
+ import { useSeasonsManagement } from "../../composable/useSeasonsManagement";
175
+ import { SeasonMode } from "../../../stores/class/general/emission";
166
176
 
167
177
  import { defineAsyncComponent, toRefs, computed } from "vue";
168
178
  const ErrorMessage = defineAsyncComponent(
@@ -193,8 +203,6 @@ const Countdown = defineAsyncComponent(() => import("../live/CountDown.vue"));
193
203
  const TagList = defineAsyncComponent(() => import("./TagList.vue"));
194
204
  const ShareAnonymous = defineAsyncComponent(() => import("../sharing/ShareAnonymous.vue"));
195
205
  const PodcastRubriqueList = defineAsyncComponent(() => import("./PodcastRubriqueList.vue"));
196
- import { useI18n } from "vue-i18n";
197
- import { useRouter } from "vue-router";
198
206
 
199
207
  //Props
200
208
  const props = defineProps<{
@@ -222,6 +230,7 @@ const {
222
230
  } = usePodcastView(propsRef.podcast, propsRef.podcastConference);
223
231
  const authStore = useAuthStore();
224
232
  const router = useRouter();
233
+ const { areSeasonsEnabled } = useSeasonsManagement();
225
234
 
226
235
  //Computed
227
236
  const podcastRubriques = computed(() => {
@@ -279,6 +288,14 @@ const tags = computed(() => {
279
288
  return tags;
280
289
  });
281
290
 
291
+ const showSeasonNumber = computed((): boolean => {
292
+ return areSeasonsEnabled(props.podcast.emission) && props.podcast.seasonNumber !== undefined;
293
+ });
294
+
295
+ const showSeasonEpisodeNumber = computed((): boolean => {
296
+ return props.podcast.emission.seasonMode === SeasonMode.SEASON_WITH_PODCAST_NUMBERING && props.podcast.seasonEpisodeNumber !== undefined;
297
+ });
298
+
282
299
  //Methods
283
300
  function formatCredits(credits: string|undefined): string {
284
301
  if (credits === undefined) {
@@ -11,6 +11,7 @@
11
11
  :rubrique-id="rubriqueId"
12
12
  :no-rubriquage-id="noRubriquageId"
13
13
  :title-tag="titleTag"
14
+ :no-sort="noSort"
14
15
  @sort-chrono="sortChrono"
15
16
  @sort-popular="sortPopular"
16
17
  >
@@ -59,10 +60,14 @@ const props = defineProps({
59
60
  rubriquageId: { default: () => [], type: Array as () => Array<number> },
60
61
  noRubriquageId: { default: () => [], type: Array as () => Array<number> },
61
62
  query: { default: undefined, type: String },
63
+ /** Filter on season */
64
+ season: { default: undefined, type: Number },
62
65
  lastThreeMonths: { default: false, type: Boolean },
63
66
  titleTag: { default: "h2", type: String },
64
67
  /** The podcast from which suggestions are made */
65
- podcastId: { type: Number }
68
+ podcastId: { type: Number },
69
+ /** Hide sort options */
70
+ noSort: { default: false, type: Boolean }
66
71
  })
67
72
 
68
73
  //Emits
@@ -104,6 +109,7 @@ onBeforeMount(()=>{
104
109
 
105
110
  //Methods
106
111
  async function fetchNext(): Promise<void> {
112
+ // TODO use podcastApi
107
113
  const data = await classicApi.fetchData<ListClassicReturn<Podcast>>({
108
114
  api: 0,
109
115
  path: "podcast/search",
@@ -123,10 +129,10 @@ async function fetchNext(): Promise<void> {
123
129
  sort: popularSort.value ? "POPULARITY" : "DATE",
124
130
  query: props.query,
125
131
  includeStatus: ["READY", "PROCESSING"],
126
- after:
127
- popularSort.value && props.lastThreeMonths
132
+ after: popularSort.value && props.lastThreeMonths
128
133
  ? dayjs().subtract(3, "months").toISOString()
129
134
  : undefined,
135
+ season: props.season
130
136
  },
131
137
  specialTreatement: true,
132
138
  });
@@ -10,12 +10,12 @@
10
10
  `selected` : true if the option is selected
11
11
  -->
12
12
  <template>
13
- <div role="radiogroup" class="d-flex" :class="isColumn ? 'flex-column' : ''">
13
+ <div role="radiogroup" class="d-flex" :class="isColumn !== false ? 'flex-column' : ''">
14
14
  <div
15
15
  v-for="option in options"
16
16
  :key="option.title"
17
17
  class="octopus-form-item"
18
- :class="isColumn ? 'd-flex flex-nowrap align-items-center' : 'me-2'"
18
+ :class="isColumn !== false ? 'd-flex flex-nowrap align-items-center' : 'me-2'"
19
19
  >
20
20
  <input
21
21
  :id="idRadio + option.value"
@@ -37,22 +37,21 @@
37
37
 
38
38
  <script setup generic="T extends { title: string; value: string|undefined; }" lang="ts">
39
39
  //Props
40
- const { textInit } = defineProps({
41
- idRadio: { default: "", type: String },
42
- isDisabled: { default: false, type: Boolean },
43
- options: {
44
- default: () => [],
45
- type: Array as () => Array<T>,
46
- },
47
- textInit: { default: undefined, type: String },
48
- isColumn: { default: true, type: Boolean },
49
- })
40
+ const { textInit, isColumn = true } = defineProps<{
41
+ options: Array<T>;
42
+ textInit?: string;
43
+ idRadio?: string;
44
+ isDisabled?: boolean;
45
+ isColumn?: boolean;
46
+ }>();
50
47
 
51
48
  //Emits
52
- const emit = defineEmits(["update:textInit"]);
49
+ const emit = defineEmits<{
50
+ (e: 'update:textInit', value: string): void;
51
+ }>();
53
52
 
54
53
  //Methods
55
- function onChange(value:string){
54
+ function onChange(value: string){
56
55
  emit('update:textInit', value)
57
56
  }
58
57
 
@@ -14,7 +14,13 @@
14
14
  :options="options?.topBarMainContent"
15
15
  />
16
16
  </header>
17
- <div v-if="generalStore.contentToDisplay" class="header-content-bg" :style="headerBackgroundImage" :class="{ scrolled: scrolled, 'header-force-blur':needToBlur }" >
17
+
18
+ <div
19
+ v-if="generalStore.contentToDisplay"
20
+ class="header-content-bg"
21
+ :style="headerBackgroundImage"
22
+ :class="{ scrolled: scrolled, 'header-force-blur':needToBlur }"
23
+ >
18
24
  <div class="header-additional-content header-content">
19
25
  <h1 v-if="!scrolled" class="text-truncate">
20
26
  {{ titleToDisplay }}
@@ -54,7 +54,7 @@
54
54
  :just-buttons="true"
55
55
  />
56
56
  <div class="ms-2 fw-bold">
57
- {{ t("Listen to the latest episode") }}
57
+ {{ messageListenEpisode }}
58
58
  </div>
59
59
  </div>
60
60
 
@@ -99,6 +99,7 @@
99
99
  :size="ps"
100
100
  :show-count="true"
101
101
  :emission-id="emissionId"
102
+ :emission="emission"
102
103
  :category-filter="false"
103
104
  :edit-right="editRight"
104
105
  :productor-id="[emission.orga.id]"
@@ -138,6 +139,7 @@ import { useSimplePageParam } from "../composable/route/useSimplePageParam";
138
139
  import ErrorMessage from "../misc/ErrorMessage.vue";
139
140
  import ClassicHelpButton from "../misc/ClassicHelpButton.vue";
140
141
  import { emissionApi } from "../../api/emissionApi";
142
+ import { useSeasonsManagement } from "../composable/useSeasonsManagement";
141
143
 
142
144
  const ShareAnonymous = defineAsyncComponent(() => import("../display/sharing/ShareAnonymous.vue"));
143
145
  const PodcastFilterList = defineAsyncComponent(
@@ -193,15 +195,16 @@ const { t } = useI18n();
193
195
  const { useProxyImageUrl } = useImageProxy();
194
196
  const { isPodcastmaker, isEditRights, authOrgaId } = useOrgaComputed();
195
197
  const { updatePathParams } = useSeoTitleUrl();
196
- const {handle403} = useErrorHandler();
198
+ const { handle403 } = useErrorHandler();
197
199
  const filterStore = useFilterStore();
198
- const generalStore= useGeneralStore();
199
- const route= useRoute();
200
+ const generalStore = useGeneralStore();
201
+ const route = useRoute();
200
202
  const {
201
203
  searchPattern,
202
204
  paginateFirst,
203
205
  isInit
204
206
  } = useSimplePageParam(props, true);
207
+ const { areSeasonsEnabled, formatSeason } = useSeasonsManagement();
205
208
 
206
209
 
207
210
  //Computed
@@ -209,9 +212,17 @@ const name = computed(() => emission.value?.name ?? "");
209
212
  const description = computed(() => emission.value?.description ?? "");
210
213
  const editRight = computed(() => isEditRights(emission.value?.orga.id));
211
214
 
215
+ const messageListenEpisode = computed((): string => {
216
+ const base = t("Listen to the latest episode");
217
+ if (lastPodcast.value !== undefined && areSeasonsEnabled(emission.value)) {
218
+ return base + ` (${formatSeason(lastPodcast.value)})`;
219
+ } else {
220
+ return base;
221
+ }
222
+ });
212
223
 
213
224
  //Watch
214
- watch(()=>props.emissionId, () => {getEmissionDetails()}, {immediate: true});
225
+ watch(() => props.emissionId, getEmissionDetails, { immediate: true });
215
226
 
216
227
 
217
228
  onBeforeUnmount(() => {
@@ -248,12 +259,13 @@ async function getEmissionDetails(): Promise<void> {
248
259
  initError();
249
260
  }
250
261
  }
251
- function podcastsFetched(podcasts: Array<Podcast>) {
262
+ function podcastsFetched(podcasts: Array<Podcast>, season: number|undefined) {
263
+ if (season !== undefined && season !== emission.value?.seasonCount) {
264
+ return;
265
+ }
266
+
252
267
  for (const podcast of podcasts) {
253
- if (
254
- "READY" === podcast.processingStatus &&
255
- podcast.availability.visibility
256
- ) {
268
+ if ("READY" === podcast.processingStatus && podcast.availability.visibility) {
257
269
  lastPodcast.value = podcast;
258
270
  return;
259
271
  }
@@ -417,5 +417,11 @@
417
417
  "Podcast": "Folge",
418
418
  "Player - Transcription - AI Warning": "Die Transkription basiert auf KI und kann Fehler enthalten, bitte teilen Sie uns dies mit.",
419
419
  "All organisations": "Alle Organisationen",
420
- "Emission subtitle": "Untertitel der Sendung"
420
+ "Emission subtitle": "Untertitel der Sendung",
421
+ "Podcast type - Full": "Standard",
422
+ "Podcast type - Trailer": "Anhänger",
423
+ "Podcast type - Bonus": "Boni",
424
+ "Podcast - Season N": "Saison {season}",
425
+ "Podcast - Season": "Jahreszeit",
426
+ "Podcast - Episode number": "Episodennummer"
421
427
  }
@@ -417,5 +417,11 @@
417
417
  "Podcast": "Episode",
418
418
  "Player - Transcription - AI Warning": "The transcription is based on AI and may contain errors, please let us know.",
419
419
  "All organisations": "All organizations",
420
- "Emission subtitle": "Subtitle of the show"
420
+ "Emission subtitle": "Subtitle of the show",
421
+ "Podcast type - Full": "Standard",
422
+ "Podcast type - Trailer": "Trailer",
423
+ "Podcast type - Bonus": "Bonuses",
424
+ "Podcast - Season N": "Saison {season}",
425
+ "Podcast - Season": "Season",
426
+ "Podcast - Episode number": "Episode number"
421
427
  }
@@ -417,5 +417,11 @@
417
417
  "Podcast": "Episodio",
418
418
  "Player - Transcription - AI Warning": "La transcripción se basa en IA y puede contener errores, háganoslo saber.",
419
419
  "All organisations": "Todas las organizaciones",
420
- "Emission subtitle": "Subtítulo del programa"
420
+ "Emission subtitle": "Subtítulo del programa",
421
+ "Podcast type - Full": "Estándar",
422
+ "Podcast type - Trailer": "Tráiler",
423
+ "Podcast type - Bonus": "Bonificaciones",
424
+ "Podcast - Season N": "Temporada {season}",
425
+ "Podcast - Season": "Estación",
426
+ "Podcast - Episode number": "Número de episodio"
421
427
  }
@@ -440,5 +440,11 @@
440
440
  "SmartLink - To emission on podcastmaker": "Page officielle de l'émission - {organisation}",
441
441
  "SmartLink - To playlist on podcastmaker": "Page officielle de la playlist - {organisation}",
442
442
  "Miniplayer - Parameters - Auto height": "Hauteur automatique",
443
- "Player - Transcription - AI Warning": "La transcription est basée sur une IA et peut comporter des erreurs, n’hésitez pas à nous en informer."
443
+ "Player - Transcription - AI Warning": "La transcription est basée sur une IA et peut comporter des erreurs, n’hésitez pas à nous en informer.",
444
+ "Podcast type - Full": "Standard",
445
+ "Podcast type - Trailer": "Trailer",
446
+ "Podcast type - Bonus": "Bonus",
447
+ "Podcast - Season N": "Saison {season}",
448
+ "Podcast - Season": "Saison",
449
+ "Podcast - Episode number": "Numéro d'épisode"
444
450
  }
@@ -418,5 +418,11 @@
418
418
  "Podcast": "Episodio",
419
419
  "Player - Transcription - AI Warning": "La trascrizione si basa sull'intelligenza artificiale e potrebbe contenere errori, faccelo sapere.",
420
420
  "All organisations": "Tutte le organizzazioni",
421
- "Emission subtitle": "Sottotitolo dello spettacolo"
421
+ "Emission subtitle": "Sottotitolo dello spettacolo",
422
+ "Podcast type - Full": "Standard",
423
+ "Podcast type - Trailer": "Rimorchio",
424
+ "Podcast type - Bonus": "Bonus",
425
+ "Podcast - Season N": "Stagione {season}",
426
+ "Podcast - Season": "Stagione",
427
+ "Podcast - Episode number": "Numero dell'episodio"
422
428
  }
@@ -416,5 +416,11 @@
416
416
  "Podcast": "Epizoda",
417
417
  "Player - Transcription - AI Warning": "Transkripcija temelji na umetni inteligenci in lahko vsebuje napake, zato nas obvestite.",
418
418
  "All organisations": "Vse organizacije",
419
- "Emission subtitle": "Podnaslov oddaje"
419
+ "Emission subtitle": "Podnaslov oddaje",
420
+ "Podcast type - Full": "Standardno",
421
+ "Podcast type - Trailer": "Napovednik",
422
+ "Podcast type - Bonus": "Bonusi",
423
+ "Podcast - Season N": "Sezona {season}",
424
+ "Podcast - Season": "Sezona",
425
+ "Podcast - Episode number": "Številka epizode"
420
426
  }
@@ -4,6 +4,18 @@ import { Person } from "../user/person";
4
4
  import { ItuneCategory } from "./ituneCategory";
5
5
  import { Annotations } from ".";
6
6
 
7
+ /**
8
+ * Season settings for emissions
9
+ */
10
+ export enum SeasonMode {
11
+ /** This emission doesn't have seasons */
12
+ NO_SEASON = 'NO_SEASON',
13
+ /** This emission has seasons and episodes are ordered by date */
14
+ SEASON_WITHOUT_PODCAST_NUMBERING = 'SEASON_WITHOUT_PODCAST_NUMBERING',
15
+ /** This emission has seasons and episodes are ordered manually */
16
+ SEASON_WITH_PODCAST_NUMBERING = 'SEASON_WITH_PODCAST_NUMBERING'
17
+ }
18
+
7
19
  /**
8
20
  * An emission
9
21
  */
@@ -40,6 +52,12 @@ export interface Emission {
40
52
  tags?: string[];
41
53
  /** The ids of groups this emission belongs to */
42
54
  groupIds?: Array<number>
55
+ /** Seasons configuration */
56
+ seasonMode: SeasonMode;
57
+ /** Number of seasons on this emission */
58
+ seasonCount: number;
59
+ /** Indicates that the emission has explicit content */
60
+ explicit?: boolean;
43
61
  }
44
62
 
45
63
  export function emptyEmissionData(orga?: Organisation): Emission {
@@ -54,5 +72,7 @@ export function emptyEmissionData(orga?: Organisation): Emission {
54
72
  rubriqueIds: [],
55
73
  monetisable: "UNDEFINED",
56
74
  limits: {},
75
+ seasonMode: SeasonMode.NO_SEASON,
76
+ seasonCount: 0
57
77
  };
58
78
  }
@@ -17,6 +17,13 @@ export enum PodcastProcessingStatus {
17
17
  All = "ALL"
18
18
  }
19
19
 
20
+ /** Type of episode */
21
+ export enum PodcastType {
22
+ FULL = 'full',
23
+ TRAILER = 'trailer',
24
+ BONUS = 'bonus'
25
+ }
26
+
20
27
  /** Describe the availability of the podcast */
21
28
  export interface PodcastAvailability {
22
29
  date?: number | null;
@@ -46,6 +53,8 @@ export interface Podcast {
46
53
  availability: PodcastAvailability;
47
54
  /** Description of the podcast */
48
55
  description?: string;
56
+ /** Optional summary of the podcast */
57
+ summary?: string;
49
58
  /** Publishing date */
50
59
  pubDate?: string;
51
60
  /** The status of the processing of the audio file */
@@ -56,6 +65,14 @@ export interface Podcast {
56
65
  ofTags?: Array<string>;
57
66
  /** List of beneficiaries/rights holders */
58
67
  beneficiaries: Array<string>;
68
+ /** Indicates that the episode has explicit content */
69
+ explicit?: boolean;
70
+ /** Type of episode */
71
+ seasonEpisodeType: PodcastType;
72
+ /** Number of season in which podcast belongs */
73
+ seasonNumber?: number;
74
+ /** Order of the episode in the season */
75
+ seasonEpisodeNumber?: number;
59
76
 
60
77
  createdAt?: string;
61
78
  createdByUserId?: string;
@@ -47,6 +47,13 @@ main, #app{
47
47
  font-family: var(--octopus-font-family);
48
48
  }
49
49
 
50
+ // Display an asterisk for required fields
51
+ label.required:after {
52
+ content: "*";
53
+ font-weight: bold;
54
+ margin-left: 3px;
55
+ }
56
+
50
57
  /** Link style */
51
58
  a{
52
59
  word-break: break-word;