Merge branch 'master' into fix-archived-stack-button

This commit is contained in:
Julius Härtl
2022-08-02 12:24:33 +02:00
committed by GitHub
312 changed files with 11766 additions and 8271 deletions

View File

@@ -75,7 +75,7 @@ export default {
const subject = this.activity.subject_rich[0]
const parameters = JSON.parse(JSON.stringify(this.activity.subject_rich[1]))
if (parameters.after && typeof parameters.after.id === 'string' && parameters.after.id.startsWith('dt:')) {
const dateTime = parameters.after.id.substr(3)
const dateTime = parameters.after.id.slice(3)
parameters.after.name = moment(dateTime).format('L LTS')
}

View File

@@ -27,17 +27,14 @@
@drop.prevent="handleDropFiles">
<slot />
<transition name="fade" mode="out-in">
<div
v-show="isDraggingOver"
<div v-show="isDraggingOver"
class="dragover">
<div class="drop-hint">
<div
class="drop-hint__icon"
<div class="drop-hint__icon"
:class="{
'icon-upload' : !isReadOnly,
'icon-error' : isReadOnly}" />
<h2
class="drop-hint__text">
<h2 class="drop-hint__text">
{{ dropHintText }}
</h2>
</div>

View File

@@ -70,130 +70,135 @@
</form>
</div>
<div v-if="board" class="board-action-buttons">
<Popover @show="filterVisible=true" @hide="filterVisible=false">
<Actions slot="trigger" :title="t('deck', 'Apply filter')">
<ActionButton v-if="isFilterActive" icon="icon-filter_set" />
<ActionButton v-else icon="icon-filter" />
</Actions>
<div v-if="filterVisible" class="filter">
<h3>{{ t('deck', 'Filter by tag') }}</h3>
<div v-for="label in labelsSorted" :key="label.id" class="filter--item">
<input
:id="label.id"
v-model="filter.tags"
type="checkbox"
class="checkbox"
:value="label.id"
@change="setFilter">
<label :for="label.id"><span class="label" :style="labelStyle(label)">{{ label.title }}</span></label>
</div>
<h3>{{ t('deck', 'Filter by assigned user') }}</h3>
<div class="filter--item">
<input
id="unassigned"
v-model="filter.unassigned"
type="checkbox"
class="checkbox"
value="unassigned"
@change="setFilter"
@click="beforeSetFilter">
<label for="unassigned">{{ t('deck', 'Unassigned') }}</label>
</div>
<div v-for="user in board.users" :key="user.uid" class="filter--item">
<input
:id="user.uid"
v-model="filter.users"
type="checkbox"
class="checkbox"
:value="user.uid"
@change="setFilter">
<label :for="user.uid"><Avatar :user="user.uid" :size="24" :disable-menu="true" /> {{ user.displayname }}</label>
</div>
<h3>{{ t('deck', 'Filter by due date') }}</h3>
<div class="filter--item">
<input
id="overdue"
v-model="filter.due"
type="radio"
class="radio"
value="overdue"
@change="setFilter"
@click="beforeSetFilter">
<label for="overdue">{{ t('deck', 'Overdue') }}</label>
</div>
<div class="filter--item">
<input
id="dueToday"
v-model="filter.due"
type="radio"
class="radio"
value="dueToday"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueToday">{{ t('deck', 'Next 24 hours') }}</label>
</div>
<div class="filter--item">
<input
id="dueWeek"
v-model="filter.due"
type="radio"
class="radio"
value="dueWeek"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueWeek">{{ t('deck', 'Next 7 days') }}</label>
</div>
<div class="filter--item">
<input
id="dueMonth"
v-model="filter.due"
type="radio"
class="radio"
value="dueMonth"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueMonth">{{ t('deck', 'Next 30 days') }}</label>
</div>
<div class="filter--item">
<input
id="noDue"
v-model="filter.due"
type="radio"
class="radio"
value="noDue"
@change="setFilter"
@click="beforeSetFilter">
<label for="noDue">{{ t('deck', 'No due date') }}</label>
</div>
<Button :disabled="!isFilterActive" @click="clearFilter">
{{ t('deck', 'Clear filter') }}
<div class="board-action-buttons__filter">
<Popover container=".board-action-buttons__filter"
:placement="'bottom-end'"
:aria-label="t('deck', 'Active filters')"
@show="filterVisible=true"
@hide="filterVisible=false">
<!-- We cannot use Actions here are the popover trigger does not update on reactive icons -->
<Button slot="trigger"
:title="t('deck', 'Apply filter')"
class="filter-button"
type="tertiary-no-background">
<template #icon>
<FilterIcon v-if="isFilterActive" :size="20" decorative />
<FilterOffIcon v-else :size="20" decorative />
</template>
</Button>
</div>
</Popover>
<div v-if="filterVisible" class="filter">
<h3>{{ t('deck', 'Filter by tag') }}</h3>
<div v-for="label in labelsSorted" :key="label.id" class="filter--item">
<input :id="label.id"
v-model="filter.tags"
type="checkbox"
class="checkbox"
:value="label.id"
@change="setFilter">
<label :for="label.id"><span class="label" :style="labelStyle(label)">{{ label.title }}</span></label>
</div>
<h3>{{ t('deck', 'Filter by assigned user') }}</h3>
<div class="filter--item">
<input id="unassigned"
v-model="filter.unassigned"
type="checkbox"
class="checkbox"
value="unassigned"
@change="setFilter"
@click="beforeSetFilter">
<label for="unassigned">{{ t('deck', 'Unassigned') }}</label>
</div>
<div v-for="user in board.users" :key="user.uid" class="filter--item">
<input :id="user.uid"
v-model="filter.users"
type="checkbox"
class="checkbox"
:value="user.uid"
@change="setFilter">
<label :for="user.uid"><Avatar :user="user.uid" :size="24" :disable-menu="true" /> {{ user.displayname }}</label>
</div>
<h3>{{ t('deck', 'Filter by due date') }}</h3>
<div class="filter--item">
<input id="overdue"
v-model="filter.due"
type="radio"
class="radio"
value="overdue"
@change="setFilter"
@click="beforeSetFilter">
<label for="overdue">{{ t('deck', 'Overdue') }}</label>
</div>
<div class="filter--item">
<input id="dueToday"
v-model="filter.due"
type="radio"
class="radio"
value="dueToday"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueToday">{{ t('deck', 'Next 24 hours') }}</label>
</div>
<div class="filter--item">
<input id="dueWeek"
v-model="filter.due"
type="radio"
class="radio"
value="dueWeek"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueWeek">{{ t('deck', 'Next 7 days') }}</label>
</div>
<div class="filter--item">
<input id="dueMonth"
v-model="filter.due"
type="radio"
class="radio"
value="dueMonth"
@change="setFilter"
@click="beforeSetFilter">
<label for="dueMonth">{{ t('deck', 'Next 30 days') }}</label>
</div>
<div class="filter--item">
<input id="noDue"
v-model="filter.due"
type="radio"
class="radio"
value="noDue"
@change="setFilter"
@click="beforeSetFilter">
<label for="noDue">{{ t('deck', 'No due date') }}</label>
</div>
<Button :disabled="!isFilterActive" :wide="true" @click="clearFilter">
{{ t('deck', 'Clear filter') }}
</Button>
</div>
</Popover>
</div>
<Actions>
<ActionButton
icon="icon-archive"
@click="toggleShowArchived">
<ActionButton @click="toggleShowArchived">
<template #icon>
<ArchiveIcon :size="20" decorative />
</template>
{{ showArchived ? t('deck', 'Hide archived cards') : t('deck', 'Show archived cards') }}
</ActionButton>
<ActionButton v-if="compactMode"
icon="icon-toggle-compact-collapsed"
@click="toggleCompactMode">
<ArrowExpandVerticalIcon slot="icon" :size="20" decorative />
{{ t('deck', 'Toggle compact mode') }}
</ActionButton>
<ActionButton v-else
icon="icon-toggle-compact-expanded"
@click="toggleCompactMode">
<ArrowCollapseVerticalIcon slot="icon" :size="20" decorative />
{{ t('deck', 'Toggle compact mode') }}
</ActionButton>
</Actions>
@@ -208,14 +213,29 @@
<script>
import { mapState, mapGetters } from 'vuex'
import { Actions, ActionButton, Popover, Avatar } from '@nextcloud/vue'
import { Actions, ActionButton, Avatar, Button, Popover } from '@nextcloud/vue'
import labelStyle from '../mixins/labelStyle'
import CardCreateDialog from '../CardCreateDialog'
import ArchiveIcon from 'vue-material-design-icons/Archive'
import FilterIcon from 'vue-material-design-icons/Filter'
import FilterOffIcon from 'vue-material-design-icons/FilterOff'
import ArrowCollapseVerticalIcon from 'vue-material-design-icons/ArrowCollapseVertical'
import ArrowExpandVerticalIcon from 'vue-material-design-icons/ArrowExpandVertical'
export default {
name: 'Controls',
components: {
Actions, ActionButton, Popover, Avatar, CardCreateDialog,
Actions,
ActionButton,
Button,
Popover,
Avatar,
CardCreateDialog,
ArchiveIcon,
FilterIcon,
FilterOffIcon,
ArrowCollapseVerticalIcon,
ArrowExpandVerticalIcon,
},
mixins: [labelStyle],
props: {
@@ -258,18 +278,12 @@ export default {
}
},
isFilterActive() {
if (this.filter.tags.length !== 0 || this.filter.users.length !== 0 || this.filter.due !== '') {
return true
}
return false
return this.filter.tags.length !== 0 || this.filter.users.length !== 0 || this.filter.due !== ''
},
labelsSorted() {
return [...this.board.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
},
beforeDestroy() {
this.setPageTitle('')
},
watch: {
board(current, previous) {
if (current?.id !== previous?.id) {
@@ -280,6 +294,9 @@ export default {
}
},
},
beforeDestroy() {
this.setPageTitle('')
},
methods: {
beforeSetFilter(e) {
if (this.filter.due === e.target.value) {
@@ -408,12 +425,6 @@ export default {
.board-action-buttons {
display: flex;
button {
border: 0;
width: 44px;
margin: 0 0 0 -1px;
background-color: transparent;
}
}
.deck-search {
@@ -441,8 +452,9 @@ export default {
}
.filter {
width: 250px;
max-height: 80vh;
width: 240px;
max-height: calc(100vh - 150px);
position: relative;
overflow: auto;
padding: 8px;
}
@@ -451,8 +463,23 @@ export default {
margin-top: 0px;
margin-bottom: 5px;
}
.filter-button {
padding: 0;
border-radius: 50%;
width: 44px;
height: 44px;
&:hover, &:focus {
background-color: rgba(127,127,127,0.25) !important;
}
}
</style>
<style lang="scss">
.popover:focus {
outline: 2px solid var(--color-main-text);
}
.tooltip-inner.popover-inner {
text-align: left;
}

View File

@@ -46,6 +46,6 @@ export default {
z-index: 100;
}
.app-deck .app-sidebar {
z-index: 20000 !important;
z-index: 1500 !important;
}
</style>

View File

@@ -163,8 +163,8 @@ export default {
<style lang="scss" scoped>
@import '../../css/animations.scss';
@import '../../css/variables.scss';
@import '../../css/animations';
@import '../../css/variables';
form {
text-align: center;
@@ -222,6 +222,7 @@ export default {
padding: $stack-spacing;
overflow-x: hidden;
overflow-y: auto;
scrollbar-gutter: stable;
padding-top: 15px;
margin-top: -10px;
}

View File

@@ -8,8 +8,7 @@
<span>{{ deletedStack.title }}</span>
<span class="timestamp">{{ relativeDate(deletedStack.deletedAt*1000) }}</span>
</div>
<button
:title="t('settings', 'Undo')"
<button :title="t('settings', 'Undo')"
class="app-navigation-entry-deleted-button icon-history"
@click="stackUndoDelete(deletedStack)" />
</li>
@@ -23,8 +22,7 @@
<span>{{ deletedCard.title }}</span>
<span class="timestamp">{{ relativeDate(deletedCard.deletedAt*1000) }}</span>
</div>
<button
:title="t('settings', 'Undo')"
<button :title="t('settings', 'Undo')"
class="app-navigation-entry-deleted-button icon-history"
@click="cardUndoDelete(deletedCard)" />
</li>

View File

@@ -1,7 +1,6 @@
<template>
<div>
<Multiselect
v-if="canShare"
<Multiselect v-if="canShare"
v-model="addAcl"
:placeholder="t('deck', 'Share board with a user, group or circle …')"
:options="formatedSharees"
@@ -21,8 +20,7 @@
</template>
</Multiselect>
<ul
id="shareWithList"
<ul id="shareWithList"
class="shareWithList">
<li>
<Avatar :user="board.owner.uid" />
@@ -200,7 +198,7 @@ export default {
},
clickTransferOwner(newOwner) {
OC.dialogs.confirmDestructive(
t('deck', 'Are you sure you want to transfer the board {title} for {user}?', { title: this.board.title, user: newOwner }),
t('deck', 'Are you sure you want to transfer the board {title} to {user}?', { title: this.board.title, user: newOwner }),
t('deck', 'Transfer the board.'),
{
type: OC.dialogs.YES_NO_BUTTONS,
@@ -214,13 +212,13 @@ export default {
this.isLoading = true
await this.$store.dispatch('transferOwnership', {
boardId: this.board.id,
newOwner
newOwner,
})
const successMessage = t('deck', 'Transfer the board for {user} successfully', { user: newOwner })
const successMessage = t('deck', 'The board has been transferred to {user}', { user: newOwner })
showSuccess(successMessage)
this.$router.push({ name: 'main' })
} catch (e) {
const errorMessage = t('deck', 'Failed to transfer the board for {user}', { user: newOwner.user })
const errorMessage = t('deck', 'Failed to transfer the board to {user}', { user: newOwner.user })
showError(errorMessage)
} finally {
this.isLoading = false

View File

@@ -23,19 +23,29 @@
<template>
<div class="stack">
<div v-click-outside="stopCardCreation" class="stack__header" :class="{'stack__header--add': showAddCard }">
<div v-click-outside="stopCardCreation"
class="stack__header"
:class="{'stack__header--add': showAddCard }"
tabindex="0"
:aria-label="stack.title">
<transition name="fade" mode="out-in">
<h3 v-if="!canManage || isArchived">
{{ stack.title }}
</h3>
<h3 v-else-if="!editing"
v-tooltip="stack.title"
tabindex="0"
:aria-label="stack.title"
class="stack__title"
@click="startEditing(stack)">
@click="startEditing(stack)"
@keydown.enter="startEditing(stack)">
{{ stack.title }}
</h3>
<form v-else @submit.prevent="finishedEdit(stack)">
<input v-model="copiedStack.title" v-focus type="text">
<input v-model="copiedStack.title"
v-focus
type="text"
required="required">
<input v-tooltip="t('deck', 'Add a new list')"
class="icon-confirm"
type="submit"
@@ -44,9 +54,15 @@
</transition>
<Actions v-if="canManage && !isArchived" :force-menu="true">
<ActionButton v-if="!showArchived" icon="icon-archive" @click="modalArchivAllCardsShow=true">
<template #icon>
<ArchiveIcon decorative />
</template>
{{ t('deck', 'Archive all cards') }}
</ActionButton>
<ActionButton v-if="showArchived" icon="icon-archive" @click="modalArchivAllCardsShow=true">
<ActionButton v-if="showArchived" @click="modalArchivAllCardsShow=true">
<template #icon>
<ArchiveIcon decorative />
</template>
{{ t('deck', 'Unarchive all cards') }}
</ActionButton>
<ActionButton icon="icon-delete" @click="deleteStack(stack)">
@@ -111,12 +127,14 @@
non-drag-area-selector=".dragDisabled"
:drag-handle-selector="dragHandleSelector"
@should-accept-drop="canEdit"
@drag-start="draggingCard = true"
@drag-end="draggingCard = false"
@drop="($event) => onDropCard(stack.id, $event)">
<Draggable v-for="card in cardsByStack" :key="card.id">
<transition :appear="animate && !card.animated && (card.animated=true)"
:appear-class="'zoom-appear-class'"
:appear-active-class="'zoom-appear-active-class'">
<CardItem :id="card.id" />
<CardItem :id="card.id" :dragging="draggingCard" />
</transition>
</Draggable>
</Container>
@@ -133,6 +151,7 @@ import { showError, showUndo } from '@nextcloud/dialogs'
import CardItem from '../cards/CardItem'
import '@nextcloud/dialogs/styles/toast.scss'
import ArchiveIcon from 'vue-material-design-icons/Archive'
export default {
name: 'Stack',
@@ -143,6 +162,7 @@ export default {
Container,
Draggable,
Modal,
ArchiveIcon,
},
props: {
@@ -154,6 +174,7 @@ export default {
data() {
return {
editing: false,
draggingCard: false,
copiedStack: '',
newCardTitle: '',
showAddCard: false,
@@ -288,7 +309,7 @@ export default {
@import './../../css/variables';
.stack {
width: $stack-width + $stack-spacing*3;
width: $stack-width + $stack-spacing * 3;
margin-left: math.div($stack-spacing, 2);
margin-right: math.div($stack-spacing, 2);
}
@@ -337,36 +358,47 @@ export default {
flex-grow: 1;
display: flex;
cursor: inherit;
margin: 0;
input[type=text] {
flex-grow: 1;
}
}
}
.stack__title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc($stack-width - 60px);
h3.stack__title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc($stack-width - 60px);
border-radius: 3px;
margin: 6px;
padding: 4px 4px;
&:focus {
outline: 2px solid var(--color-border-dark);
border-radius: 3px;
}
}
form {
margin: 2px 0;
}
}
.stack__card-add {
width: $stack-width;
height: 44px;
flex-shrink: 0;
z-index: 100;
display: flex;
margin-left: 12px;
margin-right: 12px;
margin-top: 5px;
margin-bottom: 20px;
background-color: var(--color-main-background);
form {
display: flex;
margin-left: 12px;
margin-right: 12px;
width: 100%;
margin: 0;
box-shadow: 0 0 3px var(--color-box-shadow);
border-radius: var(--border-radius-large);
overflow: hidden;

View File

@@ -109,7 +109,7 @@ import relativeDate from '../../mixins/relativeDate'
import { formatFileSize } from '@nextcloud/files'
import { getCurrentUser } from '@nextcloud/auth'
import { generateUrl, generateOcsUrl, generateRemoteUrl } from '@nextcloud/router'
import { mapState } from 'vuex'
import { mapState, mapActions } from 'vuex'
import { loadState } from '@nextcloud/initial-state'
import attachmentUpload from '../../mixins/attachmentUpload'
import { getFilePickerBuilder } from '@nextcloud/dialogs'
@@ -205,11 +205,14 @@ export default {
cardId: {
immediate: true,
handler() {
this.$store.dispatch('fetchAttachments', this.cardId)
this.fetchAttachments(this.cardId)
},
},
},
methods: {
...mapActions([
'fetchAttachments',
]),
handleUploadFile(event) {
const files = event.target.files ?? []
for (const file of files) {
@@ -233,7 +236,7 @@ export default {
shareType: 12,
shareWith: '' + this.cardId,
}).then(() => {
this.$store.dispatch('fetchAttachments', this.cardId)
this.fetchAttachments(this.cardId)
})
})
},

View File

@@ -57,13 +57,14 @@
<AppSidebarTab id="attachments"
:order="1"
:name="t('deck', 'Attachments')"
icon="icon-attach">
:name="t('deck', 'Attachments')">
<template #icon>
<AttachmentIcon size="20" decorative />
</template>
<CardSidebarTabAttachments :card="currentCard" />
</AppSidebarTab>
<AppSidebarTab
id="comments"
<AppSidebarTab id="comments"
:order="2"
:name="t('deck', 'Comments')"
icon="icon-comment">
@@ -90,6 +91,7 @@ import CardSidebarTabComments from './CardSidebarTabComments'
import CardSidebarTabActivity from './CardSidebarTabActivity'
import relativeDate from '../../mixins/relativeDate'
import moment from '@nextcloud/moment'
import AttachmentIcon from 'vue-material-design-icons/Paperclip.vue'
import { showError } from '@nextcloud/dialogs'
import { getLocale } from '@nextcloud/l10n'
@@ -106,6 +108,7 @@ export default {
CardSidebarTabComments,
CardSidebarTabActivity,
CardSidebarTabDetails,
AttachmentIcon,
},
mixins: [relativeDate],
props: {
@@ -217,14 +220,18 @@ export default {
.modal__card .app-sidebar {
$modal-padding: 14px;
border: 0;
min-width: calc(100% - #{$modal-padding*2});
min-width: calc(100% - #{$modal-padding * 2});
position: relative;
top: 0;
left: 0;
right: 0;
max-width: calc(100% - #{$modal-padding*2});
max-width: calc(100% - #{$modal-padding * 2});
padding: 0 14px;
max-height: 100%;
overflow: initial;
user-select: text;
-webkit-user-select: text;
&::v-deep {
.app-sidebar-header {
position: sticky;
@@ -240,6 +247,10 @@ export default {
background-color: var(--color-main-background);
}
.app-sidebar__tab {
overflow: initial;
}
#emptycontent, .emptycontent {
margin-top: 88px;
}

View File

@@ -21,8 +21,7 @@
-->
<template>
<AttachmentList
:card-id="card.id"
<AttachmentList :card-id="card.id"
:removable="true"
@delete-attachment="deleteAttachment"
@restore-attachment="restoreAttachment" />

View File

@@ -388,9 +388,11 @@ export default {
.section-details {
flex-grow: 1;
display: flex;
flex-wrap: wrap;
button.action-item--single {
margin-top: -6px;
margin-top: -3px;
}
}
}

View File

@@ -25,7 +25,10 @@
{{ comment.actorDisplayName }}
</span>
<Actions v-show="!edit" :force-menu="true">
<ActionButton icon="icon-reply" :close-after-click="true" @click="replyTo()">
<ActionButton :close-after-click="true" @click="replyTo()">
<template #icon>
<ReplyIcon decorative />
</template>
{{ t('deck', 'Reply') }}
</ActionButton>
<ActionButton v-if="canEdit"
@@ -51,8 +54,7 @@
</div>
<CommentItem v-if="comment.replyTo" :reply="true" :comment="comment.replyTo" />
<div v-show="!edit" ref="richTextElement">
<RichText
class="comment--content"
<RichText class="comment--content"
:text="richText(comment)"
:arguments="richArgs(comment)"
:autolink="true" />
@@ -68,6 +70,7 @@ import CommentForm from './CommentForm'
import { getCurrentUser } from '@nextcloud/auth'
import md5 from 'blueimp-md5'
import relativeDate from '../../mixins/relativeDate'
import ReplyIcon from 'vue-material-design-icons/Reply'
const AtMention = {
name: 'AtMention',
@@ -91,6 +94,7 @@ export default {
ActionButton,
CommentForm,
RichText,
ReplyIcon,
},
mixins: [relativeDate],
props: {

View File

@@ -39,7 +39,10 @@
</ActionButton>
</Actions>
<Actions v-if="canEdit">
<ActionButton v-if="descriptionEditing" icon="icon-attach" @click="showAttachmentModal()">
<ActionButton v-if="descriptionEditing" @click="showAttachmentModal()">
<template #icon>
<PaperclipIcon :size="24" decorative />
</template>
{{ t('deck', 'Add Attachment') }}
</ActionButton>
</Actions>
@@ -63,8 +66,7 @@
<Modal v-if="modalShow" :title="t('deck', 'Choose attachment')" @close="modalShow=false">
<div class="modal__content">
<h3>{{ t('deck', 'Choose attachment') }}</h3>
<AttachmentList
:card-id="card.id"
<AttachmentList :card-id="card.id"
:selectable="true"
@select-attachment="addAttachment" />
</div>
@@ -74,18 +76,19 @@
<script>
import MarkdownIt from 'markdown-it'
import MarkdownItTaskLists from 'markdown-it-task-lists'
import MarkdownItTaskCheckbox from 'markdown-it-task-checkbox'
import MarkdownItLinkAttributes from 'markdown-it-link-attributes'
import AttachmentList from './AttachmentList'
import { Actions, ActionButton, Modal } from '@nextcloud/vue'
import { formatFileSize } from '@nextcloud/files'
import { generateUrl } from '@nextcloud/router'
import { mapState, mapGetters } from 'vuex'
import PaperclipIcon from 'vue-material-design-icons/Paperclip'
const markdownIt = new MarkdownIt({
linkify: true,
})
markdownIt.use(MarkdownItTaskLists, { enabled: true, label: true, labelAfter: true })
markdownIt.use(MarkdownItTaskCheckbox, { disabled: false, idPrefix: 'task-item-', ulClass: 'contains-task-list' })
markdownIt.use(MarkdownItLinkAttributes, {
attrs: {
@@ -102,6 +105,7 @@ export default {
ActionButton,
Modal,
AttachmentList,
PaperclipIcon,
},
props: {
card: {
@@ -264,6 +268,7 @@ export default {
overflow-x: auto;
&::v-deep {
/* stylelint-disable-next-line no-invalid-position-at-import-rule */
@import './../../css/markdown';
}
@@ -324,6 +329,12 @@ h5 {
border-left: 1px solid var(--color-main-text);
}
.CodeMirror-selected,
.CodeMirror-line::selection, .CodeMirror-line>span::selection, .CodeMirror-line>span>span::selection {
background: var(--color-primary-element) !important;
color: var(--color-primary-text) !important;
}
.editor-preview,
.editor-statusbar {
display: none;

View File

@@ -31,6 +31,7 @@
:user="user.participant.uid"
:display-name="user.participant.displayname"
:disable-menu="true"
:show-user-status="false"
:size="32" />
<Avatar v-if="user.type === 1"
:user="user.participant.uid"
@@ -56,8 +57,7 @@
<div class="avatar-print-list">
<div v-for="user in avatarUsers" :key="user.id" class="avatar-print-list-item">
<Avatar
class="avatar-print-list-avatar"
<Avatar class="avatar-print-list-avatar"
:user="user.participant.uid"
:display-name="user.participant.displayname"
:disable-menu="true"

View File

@@ -24,19 +24,23 @@
<div v-if="card" class="badges">
<div v-if="card.commentsCount > 0"
v-tooltip="commentsHint"
class="icon icon-comment"
:class="{ 'icon-comment--unread': card.commentsUnread > 0 }"
class="icon-badge"
@click.stop="openComments">
{{ card.commentsCount }}
<CommentUnreadIcon v-if="card.commentsUnread > 0" size="16" />
<CommentIcon v-else size="16" />
<span>{{ card.commentsCount }}</span>
</div>
<div v-if="card.description && checkListCount > 0" class="card-tasks icon icon-checkmark">
{{ checkListCheckedCount }}/{{ checkListCount }}
<div v-if="card.description && checkListCount > 0" class="icon-badge">
<CheckmarkIcon :size="16" :title="t('deck', 'Todo items')" />
<span>{{ checkListCheckedCount }}/{{ checkListCount }}</span>
</div>
<div v-else-if="card.description.trim() && checkListCount == 0" class="icon icon-description" />
<div v-if="card.attachmentCount > 0" class="icon-attach icon icon-attach-dark">
{{ card.attachmentCount }}
<TextIcon v-else-if="card.description.trim() && checkListCount == 0" size="16" decorative />
<div v-if="card.attachmentCount > 0" class="icon-badge">
<AttachmentIcon size="16" />
<span>{{ card.attachmentCount }}</span>
</div>
<AvatarList :users="card.assignedUsers" />
@@ -47,10 +51,15 @@
<script>
import AvatarList from './AvatarList'
import CardMenu from './CardMenu'
import TextIcon from 'vue-material-design-icons/Text.vue'
import AttachmentIcon from 'vue-material-design-icons/Paperclip.vue'
import CheckmarkIcon from 'vue-material-design-icons/CheckboxMarked.vue'
import CommentIcon from 'vue-material-design-icons/Comment.vue'
import CommentUnreadIcon from 'vue-material-design-icons/CommentAccount.vue'
export default {
name: 'CardBadges',
components: { AvatarList, CardMenu },
components: { AvatarList, CardMenu, TextIcon, AttachmentIcon, CheckmarkIcon, CommentIcon, CommentUnreadIcon },
props: {
card: {
type: Object,
@@ -89,18 +98,13 @@ export default {
width: 100%;
flex-grow: 1;
.icon {
opacity: 0.5;
padding: 10px 20px;
padding-right: 4px;
margin-right: 5px;
background-position: left;
background-size: 16px;
.icon-badge {
opacity: .7;
display: flex;
margin-right: 2px;
span {
margin-left: 18px;
}
&.icon-comment--unread {
opacity: 1;
padding: 10px 2px;
}
}
}

View File

@@ -35,14 +35,18 @@
{{ board.title }} » {{ stack.title }}
</div>
<div class="card-upper">
<h3 v-if="compactMode || isArchived || showArchived || !canEdit || standalone">
<h3 v-if="inlineEditingBlocked">
{{ card.title }}
</h3>
<h3 v-else-if="!editing">
<span @click.stop="startEditing(card)">{{ card.title }}</span>
<h3 v-else-if="!editing"
tabindex="0"
class="editable"
:aria-label="t('deck', 'Edit card title')"
@click.stop="startEditing(card)"
@keydown.enter.stop.prevent="startEditing(card)">
{{ card.title }}
</h3>
<form v-if="editing"
<form v-else-if="editing"
v-click-outside="cancelEdit"
class="dragDisabled"
@click.stop
@@ -106,6 +110,10 @@ export default {
type: Boolean,
default: false,
},
dragging: {
type: Boolean,
default: false,
},
},
data() {
return {
@@ -135,6 +143,9 @@ export default {
const board = this.$store.getters.boards.find((item) => item.id === this.card.boardId)
return board ? !board.archived && board.permissions.PERMISSION_EDIT : false
},
inlineEditingBlocked() {
return this.compactMode || this.isArchived || this.showArchived || !this.canEdit || this.standalone
},
card() {
return this.item ? this.item : this.$store.getters.cardById(this.id)
},
@@ -154,6 +165,9 @@ export default {
},
methods: {
openCard() {
if (this.dragging) {
return
}
const boardId = this.card && this.card.boardId ? this.card.boardId : this.$route.params.id
this.$router.push({ name: 'card', params: { id: boardId, cardId: this.card.id } }).catch(() => {})
},
@@ -171,6 +185,9 @@ export default {
this.editing = false
},
applyLabelFilter(label) {
if (this.dragging) {
return
}
this.$nextTick(() => this.$store.dispatch('toggleFilter', { tags: [label.id] }))
},
},
@@ -217,14 +234,20 @@ export default {
}
h3 {
margin: 12px $card-padding;
margin: 5px $card-padding;
padding: 6px;
flex-grow: 1;
font-size: 100%;
overflow: hidden;
word-wrap: break-word;
padding-left: 4px;
span {
&.editable {
cursor: text;
&:focus {
outline: 2px solid var(--color-border-dark);
border-radius: 3px;
}
}
}
input[type=text] {
@@ -232,6 +255,7 @@ export default {
}
}
/* stylelint-disable-next-line no-invalid-position-at-import-rule */
@import './../../css/labels';
.card-controls {

View File

@@ -40,9 +40,13 @@
{{ t('deck', 'Move card') }}
</ActionButton>
<ActionButton icon="icon-settings-dark" :close-after-click="true" @click="openCard">
<CardBulletedIcon slot="icon" :size="20" decorative />
{{ t('deck', 'Card details') }}
</ActionButton>
<ActionButton icon="icon-archive" :close-after-click="true" @click="archiveUnarchiveCard()">
<ActionButton :close-after-click="true" @click="archiveUnarchiveCard()">
<template #icon>
<ArchiveIcon :size="20" decorative />
</template>
{{ card.archived ? t('deck', 'Unarchive card') : t('deck', 'Archive card') }}
</ActionButton>
<ActionButton v-if="showArchived === false"
@@ -90,10 +94,12 @@ import { generateUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { showUndo } from '@nextcloud/dialogs'
import '@nextcloud/dialogs/styles/toast.scss'
import ArchiveIcon from 'vue-material-design-icons/Archive'
import CardBulletedIcon from 'vue-material-design-icons/CardBulleted'
export default {
name: 'CardMenu',
components: { Actions, ActionButton, Modal, Multiselect },
components: { Actions, ActionButton, Modal, Multiselect, ArchiveIcon, CardBulletedIcon },
props: {
card: {
type: Object,
@@ -135,7 +141,11 @@ export default {
},
activeBoards() {
return this.$store.getters.boards.filter((item) => item.deletedAt === 0 && item.archived === false)
}
},
boardId() {
return this.card?.boardId ? this.card.boardId : this.$route.params.id
},
},
methods: {
openCard() {
@@ -167,10 +177,13 @@ export default {
},
})
},
moveCard() {
async moveCard() {
this.copiedCard = Object.assign({}, this.card)
this.copiedCard.stackId = this.selectedStack.id
this.$store.dispatch('moveCard', this.copiedCard)
if (parseInt(this.boardId) === parseInt(this.selectedStack.boardId)) {
await this.$store.commit('addNewCard', { ...this.copiedCard })
}
this.modalShow = false
},
async loadStacksFromBoard(board) {

View File

@@ -23,26 +23,25 @@
<template>
<AppNavigationVue :class="{'icon-loading': loading}">
<template #list>
<AppNavigationItem
:title="t('deck', 'Upcoming cards')"
<AppNavigationItem :title="t('deck', 'Upcoming cards')"
icon="icon-calendar-dark"
:exact="true"
to="/" />
<AppNavigationBoardCategory
id="deck-navigation-all"
<AppNavigationBoardCategory id="deck-navigation-all"
to="/board"
:text="t('deck', 'All boards')"
:boards="noneArchivedBoards"
:open-on-add-boards="true"
icon="icon-deck" />
<AppNavigationBoardCategory
id="deck-navigation-archived"
<AppNavigationBoardCategory id="deck-navigation-archived"
to="/board/archived"
:text="t('deck', 'Archived boards')"
:boards="archivedBoards"
icon="icon-archive" />
<AppNavigationBoardCategory
id="deck-navigation-shared"
:boards="archivedBoards">
<template #icon>
<ArchiveIcon :size="20" decorative />
</template>
</AppNavigationBoardCategory>
<AppNavigationBoardCategory id="deck-navigation-shared"
to="/board/shared"
:text="t('deck', 'Shared with you')"
:boards="sharedBoards"
@@ -50,7 +49,7 @@
<AppNavigationAddBoard v-if="canCreate" />
</template>
<template #footer>
<AppNavigationSettings>
<AppNavigationSettings :title="t('deck', 'Deck settings')">
<div>
<div>
<input id="toggle-modal"
@@ -102,6 +101,7 @@ import AppNavigationBoardCategory from './AppNavigationBoardCategory'
import { loadState } from '@nextcloud/initial-state'
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import ArchiveIcon from 'vue-material-design-icons/Archive'
const canCreateState = loadState('deck', 'canCreate')
@@ -114,6 +114,7 @@ export default {
AppNavigationBoardCategory,
Multiselect,
AppNavigationItem,
ArchiveIcon,
},
directives: {
ClickOutside,

View File

@@ -35,8 +35,7 @@
<template v-if="!deleted" slot="actions">
<template v-if="!isDueSubmenuActive">
<ActionButton
icon="icon-info"
<ActionButton icon="icon-info"
:close-after-click="true"
@click="actionDetails">
{{ t('deck', 'Board details') }}
@@ -48,21 +47,27 @@
{{ t('deck', 'Edit board') }}
</ActionButton>
<ActionButton v-if="canManage && !board.archived"
icon="icon-clone"
:close-after-click="true"
@click="actionClone">
<template #icon>
<CloneIcon :size="20" decorative />
</template>
{{ t('deck', 'Clone board') }}
</ActionButton>
<ActionButton v-if="canManage && board.archived"
icon="icon-archive"
:close-after-click="true"
@click="actionUnarchive">
<template #icon>
<ArchiveIcon :size="20" decorative />
</template>
{{ t('deck', 'Unarchive board') }}
</ActionButton>
<ActionButton v-else-if="canManage && !board.archived"
icon="icon-archive"
:close-after-click="true"
@click="actionArchive">
<template #icon>
<ArchiveIcon :size="20" decorative />
</template>
{{ t('deck', 'Archive board') }}
</ActionButton>
@@ -73,31 +78,27 @@
<!-- Due date reminder settings -->
<template v-if="isDueSubmenuActive">
<ActionButton
:icon="updateDueSetting ? 'icon-loading-small' : 'icon-view-previous'"
<ActionButton :icon="updateDueSetting ? 'icon-loading-small' : 'icon-view-previous'"
:disabled="updateDueSetting"
@click="isDueSubmenuActive=false">
{{ t('deck', 'Due date reminders') }}
</ActionButton>
<ActionButton
name="notification"
<ActionButton name="notification"
icon="icon-sound"
:disabled="updateDueSetting"
:class="{ 'forced-active': board.settings['notify-due'] === 'all' }"
@click="updateSetting('notify-due', 'all')">
{{ t('deck', 'All cards') }}
</ActionButton>
<ActionButton
name="notification"
<ActionButton name="notification"
icon="icon-user"
:disabled="updateDueSetting"
:class="{ 'forced-active': board.settings['notify-due'] === 'assigned' }"
@click="updateSetting('notify-due', 'assigned')">
{{ t('deck', 'Assigned cards') }}
</ActionButton>
<ActionButton
name="notification"
<ActionButton name="notification"
icon="icon-sound-off"
:disabled="updateDueSetting"
:class="{ 'forced-active': board.settings['notify-due'] === 'off' }"
@@ -138,6 +139,8 @@
<script>
import { AppNavigationIconBullet, AppNavigationCounter, AppNavigationItem, ColorPicker, Actions, ActionButton } from '@nextcloud/vue'
import ClickOutside from 'vue-click-outside'
import ArchiveIcon from 'vue-material-design-icons/Archive'
import CloneIcon from 'vue-material-design-icons/ContentDuplicate'
export default {
name: 'AppNavigationBoard',
@@ -148,6 +151,8 @@ export default {
ColorPicker,
Actions,
ActionButton,
ArchiveIcon,
CloneIcon,
},
directives: {
ClickOutside,

View File

@@ -28,6 +28,9 @@
:allow-collapse="collapsible"
:open="opened">
<AppNavigationBoard v-for="board in boardsSorted" :key="board.id" :board="board" />
<template #icon>
<slot name="icon" />
</template>
</AppNavigationItem>
</template>

View File

@@ -173,7 +173,7 @@ export default {
</script>
<style lang="scss" scoped>
@import '../../css/variables.scss';
@import '../../css/variables';
.global-search {
width: 100%;

View File

@@ -18,8 +18,7 @@
</linearGradient>
</defs>
</svg>
<svg
class="card-placeholder__placeholder"
<svg class="card-placeholder__placeholder"
:class="{ 'standalone': standalone }"
xmlns="http://www.w3.org/2000/svg"
fill="url(#card-placeholder__gradient)">
@@ -55,7 +54,7 @@ export default {
</script>
<style lang="scss" scoped>
@import '../../css/variables.scss';
@import '../../css/variables';
$clickable-area: 44px;
.card--placeholder {