Skip to content

Commit 045277a

Browse files
committed
Show draft token in channelEdit view
1 parent b881d73 commit 045277a

13 files changed

Lines changed: 275 additions & 3 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<template>
2+
3+
<KModal :title="previewYourDraftTitle$()">
4+
<template #default>
5+
<div>
6+
<strong>{{ draftTokenLabel$() }}</strong>
7+
<StudioCopyToken
8+
:token="channel.draft_token"
9+
:showLabel="false"
10+
/>
11+
<p class="mt-16">{{ channelTokenDescription$() }}</p>
12+
</div>
13+
</template>
14+
<template #actions>
15+
<KButton
16+
:text="dismissAction$()"
17+
@click="handleDismiss"
18+
/>
19+
</template>
20+
</KModal>
21+
22+
</template>
23+
24+
25+
<script setup>
26+
27+
import { onMounted } from 'vue';
28+
import StudioCopyToken from 'shared/views/StudioCopyToken';
29+
import { communityChannelsStrings } from 'shared/strings/communityChannelsStrings';
30+
import { commonStrings } from 'shared/strings/commonStrings';
31+
import logging from 'shared/logging';
32+
33+
const props = defineProps({
34+
channel: {
35+
type: Object,
36+
required: true,
37+
},
38+
});
39+
40+
const emit = defineEmits(['close']);
41+
42+
const { dismissAction$ } = commonStrings;
43+
const { previewYourDraftTitle$, draftTokenLabel$, channelTokenDescription$ } =
44+
communityChannelsStrings;
45+
46+
function handleDismiss() {
47+
emit('close');
48+
}
49+
50+
onMounted(() => {
51+
if (!props.channel.draft_token) {
52+
// If there is no draft token, we can't preview the draft, so we just close the modal
53+
logging.error(
54+
'Attempted to open PreviewDraftChannelModal without a draft token. Closing modal.',
55+
{ channelId: props.channel.id },
56+
);
57+
handleDismiss();
58+
}
59+
});
60+
61+
</script>
62+
63+
64+
<style lang="scss" scoped>
65+
66+
.mt-16 {
67+
margin-top: 16px;
68+
}
69+
70+
</style>

contentcuration/contentcuration/frontend/channelEdit/components/sidePanels/PublishSidePanel.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@
218218
cancelAction$,
219219
languageLabel$,
220220
languageRequiredMessage$,
221+
draftBeingPublishedNotice$,
221222
versionNotesRequiredMessage$,
222223
} = communityChannelsStrings;
223224
@@ -372,6 +373,7 @@
372373
await Channel.publishDraft(currentChannel.value.id, {
373374
use_staging_tree: false,
374375
});
376+
store.dispatch('showSnackbarSimple', draftBeingPublishedNotice$());
375377
emit('close');
376378
} else {
377379
// `newChannelLanguage.value` is a KSelect option { value, label }, so we need to

contentcuration/contentcuration/frontend/channelEdit/views/TreeView/TreeViewBase.vue

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,12 @@
192192
</VListTileTitle>
193193
</VListTile>
194194
</template>
195+
<VListTile
196+
v-if="currentChannel && currentChannel.draft_token"
197+
@click="showPreviewDraftModal = true"
198+
>
199+
<VListTileTitle>{{ getDraftTokenAction$() }}</VListTileTitle>
200+
</VListTile>
195201
<VListTile
196202
v-if="isPublished"
197203
@click="showTokenModal = true"
@@ -277,6 +283,11 @@
277283
@delete="handleDelete"
278284
@close="showDeleteModal = false"
279285
/>
286+
<PreviewDraftChannelModal
287+
v-if="showPreviewDraftModal && currentChannel"
288+
:channel="currentChannel"
289+
@close="showPreviewDraftModal = false"
290+
/>
280291
<VSpeedDial
281292
v-if="showClipboardSpeedDial"
282293
v-model="showClipboard"
@@ -340,6 +351,7 @@
340351
<script>
341352
342353
import { mapActions, mapGetters, mapState } from 'vuex';
354+
import PreviewDraftChannelModal from '../../components/modals/PreviewDraftChannelModal.vue';
343355
import Clipboard from '../../components/Clipboard';
344356
import SyncResourcesModal from '../sync/SyncResourcesModal';
345357
import ProgressModal from '../progress/ProgressModal';
@@ -361,6 +373,8 @@
361373
import DraggableRegion from 'shared/views/draggable/DraggableRegion';
362374
import { DropEffect } from 'shared/mixins/draggable/constants';
363375
import DraggablePlaceholder from 'shared/views/draggable/DraggablePlaceholder';
376+
import { communityChannelsStrings } from 'shared/strings/communityChannelsStrings';
377+
import { commonStrings } from 'shared/strings/commonStrings';
364378
365379
export default {
366380
name: 'TreeViewBase',
@@ -381,8 +395,15 @@
381395
DraggablePlaceholder,
382396
SavingIndicator,
383397
QuickEditModal,
398+
PreviewDraftChannelModal,
384399
},
385400
mixins: [titleMixin],
401+
setup() {
402+
const { getDraftTokenAction$ } = communityChannelsStrings;
403+
return {
404+
getDraftTokenAction$,
405+
};
406+
},
386407
props: {
387408
loading: {
388409
type: Boolean,
@@ -398,6 +419,7 @@
398419
showSyncModal: false,
399420
showClipboard: false,
400421
showDeleteModal: false,
422+
showPreviewDraftModal: false,
401423
syncing: false,
402424
resubmitToCommunityLibraryModalData: null,
403425
};
@@ -533,6 +555,22 @@
533555
},
534556
immediate: true,
535557
},
558+
isDraftPublishing(newVal, oldVal) {
559+
if (!newVal && oldVal) {
560+
const { draftPublishedNotice$ } = communityChannelsStrings;
561+
const { previewAction$ } = commonStrings;
562+
const snackbarData = {
563+
text: draftPublishedNotice$(),
564+
};
565+
if (this.currentChannel.draft_token) {
566+
snackbarData.actionText = previewAction$();
567+
snackbarData.actionCallback = () => {
568+
this.showPreviewDraftModal = true;
569+
};
570+
}
571+
this.$store.dispatch('showSnackbar', snackbarData);
572+
}
573+
},
536574
},
537575
methods: {
538576
...mapActions('channel', ['deleteChannel']),

contentcuration/contentcuration/frontend/shared/composables/__tests__/useChannelVersionHistory.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ describe('useChannelVersionHistory', () => {
3737
expect(ChannelVersion.fetchCollection).toHaveBeenCalledWith({
3838
channel: 'channel-id',
3939
max_results: VERSIONS_PER_PAGE,
40+
version__gte: 0,
4041
});
4142
expect(versions.value).toEqual(mockVersions);
4243
expect(hasMore.value).toBe(true);

contentcuration/contentcuration/frontend/shared/composables/useChannelVersionHistory.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export function useChannelVersionHistory() {
4242
const response = await ChannelVersion.fetchCollection({
4343
channel: channelId,
4444
max_results: VERSIONS_PER_PAGE,
45+
version__gte: 0, // Exclude unpublished versions with version=null
4546
});
4647

4748
versions.value = response.results || [];

contentcuration/contentcuration/frontend/shared/strings/commonStrings.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,12 @@ export const commonStrings = createTranslator('CommonStrings', {
3030
message: 'Channel Details',
3131
context: 'Label for a section that displays details about a channel',
3232
},
33+
previewAction: {
34+
message: 'Preview',
35+
context: 'A label for an action that opens a preview of content',
36+
},
37+
dismissAction: {
38+
message: 'Dismiss',
39+
context: 'A label for an action that dismisses a notification or message',
40+
},
3341
});

contentcuration/contentcuration/frontend/shared/strings/communityChannelsStrings.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,32 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin
496496
message: 'Review',
497497
context: 'Action button to review a channel version submission to the Community Library',
498498
},
499+
500+
// Draft channel strings
501+
draftBeingPublishedNotice: {
502+
message: 'Draft version is being published',
503+
context: 'Label indicating that a draft version of the channel is currently being published',
504+
},
505+
draftPublishedNotice: {
506+
message: 'Draft published successfully',
507+
context: 'Label indicating that a draft version of the channel has been successfully published',
508+
},
509+
previewYourDraftTitle: {
510+
message: 'Preview your draft channel in Kolibri',
511+
context: 'Title for the modal that shows instructions to preview a draft channel in Kolibri',
512+
},
513+
channelTokenDescription: {
514+
message:
515+
'To preview your draft channel right away, simply copy the unique draft channel token. This is the sole method to access the channel.',
516+
context: 'Description for the channel token field in the draft preview instructions modal',
517+
},
518+
getDraftTokenAction: {
519+
message: 'Get draft token',
520+
context:
521+
'Button text for the action to retrieve the draft token in the draft preview instructions modal',
522+
},
523+
draftTokenLabel: {
524+
message: 'Draft token',
525+
context: 'Label for the draft token field in the draft preview instructions modal',
526+
},
499527
});

contentcuration/contentcuration/frontend/shared/views/NotificationsModal/index.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
>
2727
<VTab
2828
class="px-3"
29+
href="#"
2930
:disabled="isBusy"
3031
:value="NotificationsTab.UNREAD"
3132
>
3233
{{ unreadNotificationsLabel$() }}
3334
</VTab>
3435
<VTab
3536
class="px-3"
37+
href="#"
3638
:disabled="isBusy"
3739
:value="NotificationsTab.ALL"
3840
>

contentcuration/contentcuration/frontend/shared/views/details/StudioDetailsPanel.vue

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,23 @@
4444
:showLabel="false"
4545
/>
4646
<span v-else>
47-
{{ _details.primary_token.slice(0, 5) + '-' + _details.primary_token.slice(5) }}
47+
{{ hyphenateToken(_details.primary_token) }}
48+
</span>
49+
</template>
50+
</StudioDetailsRow>
51+
<StudioDetailsRow
52+
v-if="_details.draft_token"
53+
:label="draftTokenLabel$()"
54+
>
55+
<template #default>
56+
<StudioCopyToken
57+
v-if="!printing"
58+
:token="_details.draft_token"
59+
:style="{ maxWidth: 'max-content' }"
60+
:showLabel="false"
61+
/>
62+
<span v-else>
63+
{{ hyphenateToken(_details.draft_token) }}
4864
</span>
4965
</template>
5066
</StudioDetailsRow>
@@ -421,6 +437,8 @@
421437
import StudioDetailsRow from './StudioDetailsRow';
422438
import StudioThumbnail from 'shared/views/files/StudioThumbnail';
423439
import StudioCopyToken from 'shared/views/StudioCopyToken';
440+
import useToken from 'shared/composables/useToken';
441+
import { communityChannelsStrings } from 'shared/strings/communityChannelsStrings';
424442
425443
const DEFAULT_DETAILS = {
426444
name: '',
@@ -473,6 +491,14 @@
473491
titleMixin,
474492
metadataTranslationMixin,
475493
],
494+
setup() {
495+
const { hyphenateToken } = useToken();
496+
const { draftTokenLabel$ } = communityChannelsStrings;
497+
return {
498+
hyphenateToken,
499+
draftTokenLabel$,
500+
};
501+
},
476502
props: {
477503
// Object matching that returned by the channel details and
478504
// node details API endpoints, see backend for details of the

contentcuration/contentcuration/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,12 @@ def get_resource_count(self):
13881388
def get_human_token(self):
13891389
return self.secret_tokens.get(is_primary=True)
13901390

1391+
def get_draft_token(self):
1392+
draft_version = self.channel_versions.filter(version=None).first()
1393+
if not draft_version:
1394+
return None
1395+
return draft_version.secret_token
1396+
13911397
def get_channel_id_token(self):
13921398
return self.secret_tokens.get(token=self.id)
13931399

0 commit comments

Comments
 (0)