Skip to content

Commit

Permalink
Merge pull request #14236 from nextcloud/feat/noid/edit-drafts-frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
Antreesy authored Jan 30, 2025
2 parents e3b852b + 774682d commit d1c7079
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,30 @@
<template>
<!-- Poll card -->
<div v-if="draft" class="poll-card" @click="openDraft">
<span class="poll-card__header">
<IconPoll :size="20" />
<span>{{ name }}</span>
<span class="poll-card__header poll-card__header--draft">
<IconPoll class="poll-card__header-icon" :size="20" />
<span class="poll-card__header-name">{{ name }}</span>
<NcButton v-if="canEditPollDraft"
type="tertiary"
:title="t('spreed', 'Edit poll draft')"
:aria-label="t('spreed', 'Edit poll draft')"
@click.stop="editDraft">
<template #icon>
<IconPencil :size="20" />
</template>
</NcButton>
<NcButton type="tertiary"
:title="t('spreed', 'Delete poll draft')"
:aria-label="t('spreed', 'Delete poll draft')"
@click.stop="deleteDraft">
<template #icon>
<IconDelete :size="20" />
</template>
</NcButton>
</span>
<span class="poll-card__footer">
{{ pollFooterText }}
</span>

<NcButton class="poll-card__delete-draft"
type="tertiary"
:title="t('spreed', 'Delete poll draft')"
:aria-label="t('spreed', 'Delete poll draft')"
@click.stop="deleteDraft">
<template #icon>
<IconDelete :size="20" />
</template>
</NcButton>
</div>
<a v-else-if="!showAsButton"
v-intersection-observer="getPollData"
Expand All @@ -31,8 +38,8 @@
role="button"
@click="openPoll">
<span class="poll-card__header">
<IconPoll :size="20" />
<span>{{ name }}</span>
<IconPoll class="poll-card__header-icon" :size="20" />
<span class="poll-card__header-name">{{ name }}</span>
</span>
<span class="poll-card__footer">
{{ pollFooterText }}
Expand All @@ -52,13 +59,15 @@
import { vIntersectionObserver as IntersectionObserver } from '@vueuse/components'

import IconDelete from 'vue-material-design-icons/Delete.vue'
import IconPencil from 'vue-material-design-icons/Pencil.vue'
import IconPoll from 'vue-material-design-icons/Poll.vue'

import { t, n } from '@nextcloud/l10n'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'

import { POLL } from '../../../../../constants.ts'
import { hasTalkFeature } from '../../../../../services/CapabilitiesManager.ts'
import { usePollsStore } from '../../../../../stores/polls.ts'

export default {
Expand All @@ -67,6 +76,7 @@ export default {
components: {
NcButton,
IconDelete,
IconPencil,
IconPoll,
},

Expand Down Expand Up @@ -129,6 +139,10 @@ export default {
: t('spreed', 'Poll')
}
},

canEditPollDraft() {
return this.draft && hasTalkFeature(this.token, 'edit-draft-poll')
}
},

methods: {
Expand All @@ -144,7 +158,11 @@ export default {
},

openDraft() {
this.$emit('click', this.id)
this.$emit('click', { id: this.id, action: 'fill' })
},

editDraft() {
this.$emit('click', { id: this.id, action: 'edit' })
},

deleteDraft() {
Expand Down Expand Up @@ -186,14 +204,23 @@ export default {
&__header {
display: flex;
align-items: flex-start;
gap: 8px;
gap: calc(2 * var(--default-grid-baseline));
margin-bottom: 16px;
font-weight: bold;
white-space: normal;
word-wrap: anywhere;
margin-inline-end: var(--default-clickable-area);

:deep(.material-design-icon) {
&--draft {
gap: var(--default-grid-baseline);
}

&-name {
margin-inline-end: auto;
align-self: center;
}

&-icon {
height: var(--default-clickable-area);
margin-bottom: auto;
}
}
Expand All @@ -202,12 +229,6 @@ export default {
color: var(--color-text-maxcontrast);
white-space: normal;
}

& &__delete-draft {
position: absolute;
top: var(--default-grid-baseline);
inset-inline-end: var(--default-grid-baseline);
}
}

.poll-closed {
Expand Down
10 changes: 6 additions & 4 deletions src/components/PollViewer/PollDraftHandler.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
@click="openPollEditor" />
</div>
<template v-if="!props.editorOpened" #actions>
<NcButton @click="openPollEditor(null)">
<NcButton @click="openPollEditor({ id: null, action: 'fill' })">
{{ t('spreed', 'Create new poll') }}
</NcButton>
</template>
Expand Down Expand Up @@ -73,10 +73,12 @@ const pollDraftsLoaded = computed(() => pollsStore.draftsLoaded(props.token))

/**
* Opens poll editor pre-filled from the draft
* @param id poll draft ID
* @param payload method payload
* @param payload.id poll draft ID
* @param payload.action required action ('fill' from draft or 'edit' draft)
*/
function openPollEditor(id: number | null) {
EventBus.emit('poll-editor-open', { id, fromDrafts: !props.editorOpened, selector: props.container })
function openPollEditor({ id, action } : { id: number | null, action?: string }) {
EventBus.emit('poll-editor-open', { id, fromDrafts: !props.editorOpened, action, selector: props.container })
}
</script>

Expand Down
38 changes: 33 additions & 5 deletions src/components/PollViewer/PollEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
-->

<template>
<NcDialog :name="t('spreed', 'Create new poll')"
<NcDialog :name="dialogName"
:close-on-click-outside="!isFilled"
:container="container"
v-on="$listeners"
Expand Down Expand Up @@ -89,7 +89,7 @@
</div>
<template #actions>
<NcActions v-if="supportPollDrafts" force-menu>
<NcActionButton v-if="props.canCreatePollDrafts" :disabled="!isFilled" @click="createPollDraft">
<NcActionButton v-if="props.canCreatePollDrafts && !editingDraftId" :disabled="!isFilled" @click="createPollDraft">
<template #icon>
<IconFileEdit :size="20" />
</template>
Expand All @@ -102,7 +102,7 @@
{{ t('spreed', 'Export draft to file') }}
</NcActionLink>
</NcActions>
<NcButton type="primary" :disabled="!isFilled" @click="createPoll">
<NcButton type="primary" :disabled="!isFilled" @click="handleSubmit">
{{ createPollLabel }}
</NcButton>
</template>
Expand Down Expand Up @@ -157,6 +157,7 @@ const store = useStore()
const pollsStore = usePollsStore()

const isOpenedFromDraft = ref(false)
const editingDraftId = ref<number | null>(null)
const pollOption = ref<InstanceType<typeof NcTextField>[] | null>(null)
const pollImport = ref<HTMLInputElement | null>(null)

Expand All @@ -168,7 +169,14 @@ const pollForm = reactive<createPollParams>({
})

const isFilled = computed(() => Boolean(pollForm.question) && pollForm.options.filter(option => Boolean(option)).length >= 2)
const dialogName = computed(() => {
return editingDraftId.value ? t('spreed', 'Edit poll draft') : t('spreed', 'Create new poll')
})
const createPollLabel = computed(() => {
if (editingDraftId.value) {
return t('spreed', 'Save')
}

return store.getters.getToken() !== props.token
? t('spreed', 'Create poll in {name}', { name: store.getters.conversation(props.token).displayName },
undefined, { escape: false, sanitize: false })
Expand Down Expand Up @@ -217,7 +225,22 @@ function addOption() {
/**
* Post a poll into conversation
*/
async function createPoll() {
async function handleSubmit() {
if (editingDraftId.value) {
const pollDraft = await pollsStore.updatePollDraft({
token: props.token,
pollId: editingDraftId.value,
form: pollForm,
})
if (pollDraft) {
openPollDraftHandler()
nextTick(() => {
emit('close')
})
}
return
}

const poll = await pollsStore.createPoll({
token: props.token,
form: pollForm,
Expand All @@ -231,12 +254,17 @@ async function createPoll() {
* Pre-fills form from the draft
* @param id poll draft ID
* @param fromDrafts whether editor was opened from drafts handler
* @param action required action ('fill' from draft or 'edit' draft)
*/
function fillPollEditorFromDraft(id: number | null, fromDrafts: boolean) {
function fillPollEditorFromDraft(id: number | null, fromDrafts: boolean, action?: string) {
if (fromDrafts) {
// Show 'Back' button, do not reset until closed
isOpenedFromDraft.value = true
}
if (action === 'edit') {
// Show Edit interface
editingDraftId.value = id
}

if (id === null) {
return
Expand Down
5 changes: 3 additions & 2 deletions src/components/PollViewer/PollManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,14 @@ function openPollDraftHandler({ selector }: Events['poll-drafts-open']) {
* @param payload event payload
* @param payload.id poll draft ID to fill form with (null for empty form)
* @param payload.fromDrafts whether editor was opened from PollDraftHandler dialog
* @param payload.action required action ('fill' from draft or 'edit' draft)
* @param [payload.selector] selector to mount dialog to (body by default)
*/
function openPollEditor({ id, fromDrafts, selector }: Events['poll-editor-open']) {
function openPollEditor({ id, fromDrafts, action, selector }: Events['poll-editor-open']) {
container.value = selector
showPollEditor.value = true
nextTick(() => {
pollEditorRef.value?.fillPollEditorFromDraft(id, fromDrafts)
pollEditorRef.value?.fillPollEditorFromDraft(id, fromDrafts, action)
// Wait for editor to be mounted and filled before unmounting drafts dialog to avoid issues when inserting nodes
showPollDraftHandler.value = false
})
Expand Down
2 changes: 1 addition & 1 deletion src/services/EventBus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type Events = {
'joined-conversation': { token: string },
'message-height-changed': { heightDiff: number },
'poll-drafts-open': { selector?: string },
'poll-editor-open': { id: number | null, fromDrafts: boolean, selector?: string },
'poll-editor-open': { id: number | null, fromDrafts: boolean, action?: string, selector?: string },
'refresh-peer-list': void,
'retry-message': number,
'route-change': { from: Route, to: Route },
Expand Down
22 changes: 22 additions & 0 deletions src/services/pollService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ import type {
getPollResponse,
votePollParams,
votePollResponse,
updatePollDraftParams,
updatePollDraftResponse,
} from '../types/index.ts'

type createPollPayload = { token: string } & createPollParams
type updatePollDraftPayload = { token: string, pollId: number } & updatePollDraftParams

/**
* @param payload The payload
Expand Down Expand Up @@ -56,6 +59,24 @@ const createPollDraft = async ({ token, question, options, resultMode, maxVotes
} as createPollParams)
}

/**
* @param payload The payload
* @param payload.token The conversation token
* @param payload.pollId The id of poll draft
* @param payload.question The question of the poll
* @param payload.options The options participants can vote for
* @param payload.resultMode Result mode of the poll (0 - always visible | 1 - hidden until the poll is closed)
* @param payload.maxVotes Maximum amount of options a user can vote for (0 - unlimited | 1 - single answer)
*/
const updatePollDraft = async ({ token, pollId, question, options, resultMode, maxVotes }: updatePollDraftPayload): updatePollDraftResponse => {
return axios.post(generateOcsUrl('apps/spreed/api/v1/poll/{token}/draft/{pollId}', { token, pollId }), {
question,
options,
resultMode,
maxVotes,
} as updatePollDraftParams)
}

/**
* @param token The conversation token
*/
Expand Down Expand Up @@ -100,6 +121,7 @@ const deletePollDraft = async (token: string, pollId: string): deletePollDraftRe
export {
createPoll,
createPollDraft,
updatePollDraft,
getPollDrafts,
getPollData,
submitVote,
Expand Down
15 changes: 15 additions & 0 deletions src/stores/polls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { t } from '@nextcloud/l10n'
import {
createPoll,
createPollDraft,
updatePollDraft,
getPollDrafts,
getPollData,
submitVote,
Expand All @@ -22,6 +23,7 @@ import type {
ChatMessage,
createPollParams,
votePollParams,
updatePollDraftParams,
Poll,
PollDraft,
} from '../types/index.ts'
Expand Down Expand Up @@ -154,6 +156,19 @@ export const usePollsStore = defineStore('polls', {
}
},

async updatePollDraft({ token, pollId, form }: { token: string, pollId: number, form: updatePollDraftParams }) {
try {
const response = await updatePollDraft({ token, pollId, ...form })
this.addPollDraft({ token, draft: response.data.ocs.data })

showSuccess(t('spreed', 'Poll draft has been saved'))
return response.data.ocs.data
} catch (error) {
showError(t('spreed', 'An error occurred while saving the draft'))
console.error(error)
}
},

async submitVote({ token, pollId, optionIds }: submitVotePayload) {
try {
const response = await submitVote(token, pollId, optionIds)
Expand Down
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ export type getPollResponse = ApiResponse<operations['poll-show-poll']['response
export type getPollDraftsResponse = ApiResponse<operations['poll-get-all-draft-polls']['responses'][200]['content']['application/json']>
export type createPollParams = operations['poll-create-poll']['requestBody']['content']['application/json']
export type createPollResponse = ApiResponse<operations['poll-create-poll']['responses'][201]['content']['application/json']>
export type updatePollDraftParams = operations['poll-update-draft-poll']['requestBody']['content']['application/json']
export type updatePollDraftResponse = ApiResponse<operations['poll-update-draft-poll']['responses'][200]['content']['application/json']>
export type createPollDraftResponse = ApiResponse<operations['poll-create-poll']['responses'][200]['content']['application/json']>
export type votePollParams = Required<operations['poll-vote-poll']>['requestBody']['content']['application/json']
export type votePollResponse = ApiResponse<operations['poll-vote-poll']['responses'][200]['content']['application/json']>
Expand Down

0 comments on commit d1c7079

Please sign in to comment.