feat: abstract NotificationItem in dedicated component
This commit is contained in:
parent
72e80f637d
commit
def270bd08
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<BaseButton
|
||||
class="trigger-button"
|
||||
:aria-pressed="pressed || undefined"
|
||||
>
|
||||
<slot />
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
||||
defineProps<{
|
||||
pressed?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.trigger-button {
|
||||
cursor: pointer;
|
||||
color: var(--grey-400);
|
||||
transition: $transition;
|
||||
padding: .5rem;
|
||||
font-size: 1.25rem;
|
||||
position: relative;
|
||||
width: $navbar-icon-width;
|
||||
}
|
||||
|
||||
[aria-pressed] {
|
||||
color: var(--primary);
|
||||
}
|
||||
</style>
|
|
@ -26,14 +26,14 @@
|
|||
|
||||
<div class="navbar-end">
|
||||
<update/>
|
||||
<BaseButton
|
||||
<NavbarTriggerButton
|
||||
@click="openQuickActions"
|
||||
class="trigger-button pr-0"
|
||||
v-shortcut="'Control+k'"
|
||||
:pressed="quickActionsActive"
|
||||
:title="$t('keyboardShortcuts.quickSearch')"
|
||||
>
|
||||
<icon icon="search"/>
|
||||
</BaseButton>
|
||||
</NavbarTriggerButton>
|
||||
<notifications/>
|
||||
<div class="user">
|
||||
<dropdown class="is-right" ref="usernameDropdown">
|
||||
|
@ -101,6 +101,7 @@ import Dropdown from '@/components/misc/dropdown.vue'
|
|||
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||
import Notifications from '@/components/notifications/notifications.vue'
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
import NavbarTriggerButton from '@/components/home/NavbarTriggerButton.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import MenuButton from '@/components/home/MenuButton.vue'
|
||||
|
||||
|
@ -134,6 +135,8 @@ onMounted(async () => {
|
|||
listTitle.value.style.setProperty('--nav-username-width', `${usernameWidth}px`)
|
||||
})
|
||||
|
||||
const quickActionsActive = computed(() => baseStore.quickActionsActive)
|
||||
|
||||
function openQuickActions() {
|
||||
baseStore.setQuickActionsActive(true)
|
||||
}
|
||||
|
@ -218,26 +221,11 @@ $hamburger-menu-icon-width: 28px;
|
|||
}
|
||||
|
||||
.navbar {
|
||||
// FIXME: notifications should provide a slot for the icon instead, so that we can style it as we want
|
||||
:deep() {
|
||||
.trigger-button {
|
||||
cursor: pointer;
|
||||
color: var(--grey-400);
|
||||
padding: .5rem;
|
||||
font-size: 1.25rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
> * > .trigger-button {
|
||||
width: $navbar-icon-width;
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
.username {
|
||||
font-family: $vikunja-font;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
<template>
|
||||
<div class="single-notification">
|
||||
<div class="read-indicator" :class="{'read': notification.readAt !== null}" />
|
||||
<user
|
||||
class="user"
|
||||
v-if="notification.notification.doer"
|
||||
:user="notification.notification.doer"
|
||||
:show-username="false"
|
||||
:avatar-size="16"
|
||||
/>
|
||||
<div class="detail">
|
||||
<div>
|
||||
<span class="has-text-weight-bold mr-1" v-if="notification.notification.doer">
|
||||
{{ getDisplayName(notification.notification.doer) }}
|
||||
</span>
|
||||
<BaseButton :to="to" @click="emit('markNotificationAsRead')">
|
||||
{{ notificationText }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<span class="created" v-tooltip="formatDateLong(notification.created)">
|
||||
{{ formatDateSince(notification.created) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed} from 'vue'
|
||||
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
|
||||
import {NOTIFICATION_NAMES, type INotification} from '@/modelTypes/INotification'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import User from '@/components/misc/user.vue'
|
||||
|
||||
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {getDisplayName} from '@/models/user'
|
||||
import {getTextIdentifier} from '@/models/task'
|
||||
|
||||
const props = defineProps<{
|
||||
notification: INotification
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'markNotificationAsRead'): void
|
||||
}>()
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const userInfo = computed(() => authStore.info)
|
||||
|
||||
const to = computed(() => {
|
||||
const to = {
|
||||
name: '',
|
||||
params: {},
|
||||
}
|
||||
|
||||
switch (props.notification.name) {
|
||||
case NOTIFICATION_NAMES.TASK_COMMENT:
|
||||
case NOTIFICATION_NAMES.TASK_ASSIGNED:
|
||||
to.name = 'task.detail'
|
||||
to.params.id = props.notification.notification.task.id
|
||||
break
|
||||
case NOTIFICATION_NAMES.TASK_DELETED:
|
||||
// Nothing
|
||||
break
|
||||
case NOTIFICATION_NAMES.LIST_CREATED:
|
||||
to.name = 'task.index'
|
||||
to.params.listId = props.notification.notification.list.id
|
||||
break
|
||||
case NOTIFICATION_NAMES.TEAM_MEMBER_ADDED:
|
||||
to.name = 'teams.edit'
|
||||
to.params.id = props.notification.notification.team.id
|
||||
break
|
||||
default:
|
||||
}
|
||||
|
||||
return to
|
||||
})
|
||||
|
||||
const notificationText = computed(() => {
|
||||
const notification = props.notification.notification
|
||||
let who = ''
|
||||
|
||||
switch (props.notification.name) {
|
||||
case NOTIFICATION_NAMES.TASK_COMMENT:
|
||||
return `commented on ${getTextIdentifier(notification.task)}`
|
||||
case NOTIFICATION_NAMES.TASK_ASSIGNED:
|
||||
if (userInfo.value !== null && userInfo.value.id === notification.assignee.id) {
|
||||
who = 'you'
|
||||
} else {
|
||||
who = `${getDisplayName(notification.assignee)}`
|
||||
}
|
||||
|
||||
return `assigned ${who} to ${getTextIdentifier(notification.task)}`
|
||||
case NOTIFICATION_NAMES.TASK_DELETED:
|
||||
return `deleted ${getTextIdentifier(notification.task)}`
|
||||
case NOTIFICATION_NAMES.LIST_CREATED:
|
||||
return `created ${notification.list.title}`
|
||||
case NOTIFICATION_NAMES.TEAM_MEMBER_ADDED:
|
||||
if (userInfo.value !== null && userInfo.value.id === notification.member.id) {
|
||||
who = 'you'
|
||||
} else {
|
||||
who = `${getDisplayName(notification.member)}`
|
||||
}
|
||||
|
||||
return `added ${who} to the ${notification.team.name} team`
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.single-notification {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0;
|
||||
transition: background-color $transition;
|
||||
|
||||
&:hover {
|
||||
background: var(--grey-100);
|
||||
border-radius: $radius;
|
||||
}
|
||||
}
|
||||
|
||||
.read-indicator {
|
||||
width: .35rem;
|
||||
height: .35rem;
|
||||
background: var(--primary);
|
||||
border-radius: 100%;
|
||||
margin-left: .5rem;
|
||||
|
||||
&.read {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this deep styling of user should not be in here
|
||||
.user {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
margin: 0 .5rem;
|
||||
|
||||
span {
|
||||
font-family: $family-sans-serif;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.created {
|
||||
color: var(--grey-400);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--grey-800);
|
||||
}
|
||||
</style>
|
|
@ -1,40 +1,28 @@
|
|||
<template>
|
||||
<div class="notifications">
|
||||
<div class="is-flex is-justify-content-center">
|
||||
<BaseButton @click.stop="showNotifications = !showNotifications" class="trigger-button">
|
||||
<span class="unread-indicator" v-if="unreadNotifications > 0"></span>
|
||||
<icon icon="bell"/>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<!-- FIXME: add label -->
|
||||
<slot :togglePopup="togglePopup" :hasUnreadNotifications="hasUnreadNotifications">
|
||||
<NavbarTriggerButton
|
||||
:pressed="showNotifications"
|
||||
ref="toggleButton"
|
||||
@click="togglePopup"
|
||||
>
|
||||
<span v-if="hasUnreadNotifications" class="unread-indicator" />
|
||||
<icon icon="bell"/>
|
||||
</NavbarTriggerButton>
|
||||
</slot>
|
||||
|
||||
<!-- FIXME: create dedicated dropdown menu -->
|
||||
<CustomTransition name="fade">
|
||||
<div class="notifications-list" v-if="showNotifications" ref="popup">
|
||||
<span class="head">{{ $t('notification.title') }}</span>
|
||||
<div
|
||||
<h3 class="head">{{ $t('notification.title') }}</h3>
|
||||
<NotificationItem
|
||||
v-for="(n, index) in notifications"
|
||||
:key="n.id"
|
||||
class="single-notification"
|
||||
>
|
||||
<div class="read-indicator" :class="{'read': n.readAt !== null}"></div>
|
||||
<user
|
||||
:user="n.notification.doer"
|
||||
:show-username="false"
|
||||
:avatar-size="16"
|
||||
v-if="n.notification.doer"/>
|
||||
<div class="detail">
|
||||
<div>
|
||||
<span class="has-text-weight-bold mr-1" v-if="n.notification.doer">
|
||||
{{ getDisplayName(n.notification.doer) }}
|
||||
</span>
|
||||
<BaseButton @click="() => to(n, index)()">
|
||||
{{ n.toText(userInfo) }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<span class="created" v-tooltip="formatDateLong(n.created)">
|
||||
{{ formatDateSince(n.created) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
class="notification-item"
|
||||
:notification="n"
|
||||
@markNotificationAsRead="markNotificationAsRead(index, n)"
|
||||
/>
|
||||
<p class="nothing" v-if="notifications.length === 0">
|
||||
{{ $t('notification.none') }}<br/>
|
||||
<span class="explainer">
|
||||
|
@ -48,203 +36,118 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import {computed, onMounted, onUnmounted, ref} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {onClickOutside} from '@vueuse/core'
|
||||
|
||||
import NotificationService from '@/services/notification'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import User from '@/components/misc/user.vue'
|
||||
import { NOTIFICATION_NAMES as names, type INotification} from '@/modelTypes/INotification'
|
||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {getDisplayName} from '@/models/user'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import type {INotification} from '@/modelTypes/INotification'
|
||||
|
||||
const LOAD_NOTIFICATIONS_INTERVAL = 10000
|
||||
import NavbarTriggerButton from '@/components/home/NavbarTriggerButton.vue'
|
||||
import NotificationItem from '@/components/notifications/NotificationItem.vue'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
const NOTIFICATIONS_PULL_INTERVAL = 10000
|
||||
|
||||
const allNotifications = ref<INotification[]>([])
|
||||
const showNotifications = ref(false)
|
||||
const popup = ref(null)
|
||||
const toggleButton = ref(null)
|
||||
|
||||
function togglePopup() {
|
||||
showNotifications.value = !showNotifications.value
|
||||
}
|
||||
|
||||
onClickOutside(
|
||||
popup,
|
||||
() => {
|
||||
if (!showNotifications.value) {
|
||||
return
|
||||
}
|
||||
showNotifications.value = false
|
||||
},
|
||||
{ ignore: [toggleButton]},
|
||||
)
|
||||
|
||||
const unreadNotifications = computed(() => {
|
||||
return notifications.value.filter(n => n.readAt === null).length
|
||||
})
|
||||
const notifications = computed(() => {
|
||||
return allNotifications.value ? allNotifications.value.filter(n => n.name !== '') : []
|
||||
})
|
||||
const userInfo = computed(() => authStore.info)
|
||||
const unreadNotifications = computed(() => {
|
||||
return notifications.value.filter(n => n.readAt === null).length
|
||||
})
|
||||
|
||||
const hasUnreadNotifications = computed(() => unreadNotifications.value > 0)
|
||||
|
||||
let interval: ReturnType<typeof setInterval>
|
||||
|
||||
onMounted(() => {
|
||||
loadNotifications()
|
||||
document.addEventListener('click', hidePopup)
|
||||
interval = setInterval(loadNotifications, LOAD_NOTIFICATIONS_INTERVAL)
|
||||
interval = setInterval(loadNotifications, NOTIFICATIONS_PULL_INTERVAL)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', hidePopup)
|
||||
clearInterval(interval)
|
||||
})
|
||||
onUnmounted(() => clearInterval(interval))
|
||||
|
||||
const notificationService = new NotificationService()
|
||||
|
||||
loadNotifications()
|
||||
async function loadNotifications() {
|
||||
// We're recreating the notification service here to make sure it uses the latest api user token
|
||||
const notificationService = new NotificationService()
|
||||
allNotifications.value = await notificationService.getAll()
|
||||
}
|
||||
|
||||
function hidePopup(e) {
|
||||
if (showNotifications.value) {
|
||||
closeWhenClickedOutside(e, popup.value, () => showNotifications.value = false)
|
||||
}
|
||||
}
|
||||
|
||||
function to(n, index) {
|
||||
const to = {
|
||||
name: '',
|
||||
params: {},
|
||||
}
|
||||
|
||||
switch (n.name) {
|
||||
case names.TASK_COMMENT:
|
||||
case names.TASK_ASSIGNED:
|
||||
to.name = 'task.detail'
|
||||
to.params.id = n.notification.task.id
|
||||
break
|
||||
case names.TASK_DELETED:
|
||||
// Nothing
|
||||
break
|
||||
case names.LIST_CREATED:
|
||||
to.name = 'task.index'
|
||||
to.params.listId = n.notification.list.id
|
||||
break
|
||||
case names.TEAM_MEMBER_ADDED:
|
||||
to.name = 'teams.edit'
|
||||
to.params.id = n.notification.team.id
|
||||
break
|
||||
}
|
||||
|
||||
return async () => {
|
||||
if (to.name !== '') {
|
||||
router.push(to)
|
||||
}
|
||||
|
||||
n.read = true
|
||||
const notificationService = new NotificationService()
|
||||
allNotifications.value[index] = await notificationService.update(n)
|
||||
}
|
||||
async function markNotificationAsRead(index: number, notification: INotification) {
|
||||
allNotifications.value[index] = await notificationService.update({
|
||||
...notification,
|
||||
read: true,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notifications {
|
||||
width: $navbar-icon-width;
|
||||
.unread-indicator {
|
||||
position: absolute;
|
||||
top: .75rem;
|
||||
right: 1.15rem;
|
||||
width: .75rem;
|
||||
height: .75rem;
|
||||
|
||||
.unread-indicator {
|
||||
position: absolute;
|
||||
top: .75rem;
|
||||
right: 1.15rem;
|
||||
width: .75rem;
|
||||
height: .75rem;
|
||||
background: var(--primary);
|
||||
border-radius: 100%;
|
||||
border: 2px solid var(--white);
|
||||
}
|
||||
|
||||
background: var(--primary);
|
||||
border-radius: 100%;
|
||||
border: 2px solid var(--white);
|
||||
}
|
||||
.notifications-list {
|
||||
position: fixed;
|
||||
right: 1rem;
|
||||
margin-top: 1rem;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
|
||||
.notifications-list {
|
||||
position: fixed;
|
||||
right: 1rem;
|
||||
margin-top: 1rem;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
background: var(--white);
|
||||
width: 350px;
|
||||
max-width: calc(100vw - 2rem);
|
||||
padding: .75rem .25rem;
|
||||
border-radius: $radius;
|
||||
box-shadow: var(--shadow-sm);
|
||||
font-size: .85rem;
|
||||
|
||||
background: var(--white);
|
||||
width: 350px;
|
||||
max-width: calc(100vw - 2rem);
|
||||
padding: .75rem .25rem;
|
||||
border-radius: $radius;
|
||||
box-shadow: var(--shadow-sm);
|
||||
font-size: .85rem;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
max-height: calc(100vh - 1rem - #{$navbar-height});
|
||||
}
|
||||
|
||||
.head {
|
||||
font-family: $vikunja-font;
|
||||
font-size: 1rem;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.single-notification {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0;
|
||||
|
||||
transition: background-color $transition;
|
||||
|
||||
&:hover {
|
||||
background: var(--grey-100);
|
||||
border-radius: $radius;
|
||||
}
|
||||
|
||||
.read-indicator {
|
||||
width: .35rem;
|
||||
height: .35rem;
|
||||
background: var(--primary);
|
||||
border-radius: 100%;
|
||||
margin-left: .5rem;
|
||||
|
||||
&.read {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
margin: 0 .5rem;
|
||||
|
||||
span {
|
||||
font-family: $family-sans-serif;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.created {
|
||||
color: var(--grey-400);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--grey-800);
|
||||
}
|
||||
}
|
||||
|
||||
.nothing {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
color: var(--grey-500);
|
||||
|
||||
.explainer {
|
||||
font-size: .75rem;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: $tablet) {
|
||||
max-height: calc(100vh - 1rem - #{$navbar-height});
|
||||
}
|
||||
}
|
||||
|
||||
.head {
|
||||
font-family: $vikunja-font;
|
||||
font-size: 1rem;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.notification-item:last-child {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
|
||||
.nothing {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
color: var(--grey-500);
|
||||
}
|
||||
|
||||
.explainer {
|
||||
font-size: .75rem;
|
||||
}
|
||||
</style>
|
|
@ -3,7 +3,7 @@ import type {IUser} from './IUser'
|
|||
import type {ITask} from './ITask'
|
||||
import type {ITaskComment} from './ITaskComment'
|
||||
import type {ITeam} from './ITeam'
|
||||
import type { IList } from './IList'
|
||||
import type {IList} from './IList'
|
||||
|
||||
export const NOTIFICATION_NAMES = {
|
||||
'TASK_COMMENT': 'task.comment',
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||
import UserModel, {getDisplayName} from '@/models/user'
|
||||
import UserModel from '@/models/user'
|
||||
import TaskModel from '@/models/task'
|
||||
import TaskCommentModel from '@/models/taskComment'
|
||||
import ListModel from '@/models/list'
|
||||
import TeamModel from '@/models/team'
|
||||
|
||||
import {NOTIFICATION_NAMES, type INotification} from '@/modelTypes/INotification'
|
||||
import type { IUser } from '@/modelTypes/IUser'
|
||||
|
||||
export default class NotificationModel extends AbstractModel<INotification> implements INotification {
|
||||
id = 0
|
||||
|
@ -61,35 +60,4 @@ export default class NotificationModel extends AbstractModel<INotification> impl
|
|||
this.created = new Date(this.created)
|
||||
this.readAt = parseDateOrNull(this.readAt)
|
||||
}
|
||||
|
||||
toText(user: IUser | null = null) {
|
||||
let who = ''
|
||||
|
||||
switch (this.name) {
|
||||
case NOTIFICATION_NAMES.TASK_COMMENT:
|
||||
return `commented on ${this.notification.task.getTextIdentifier()}`
|
||||
case NOTIFICATION_NAMES.TASK_ASSIGNED:
|
||||
who = `${getDisplayName(this.notification.assignee)}`
|
||||
|
||||
if (user !== null && user.id === this.notification.assignee.id) {
|
||||
who = 'you'
|
||||
}
|
||||
|
||||
return `assigned ${who} to ${this.notification.task.getTextIdentifier()}`
|
||||
case NOTIFICATION_NAMES.TASK_DELETED:
|
||||
return `deleted ${this.notification.task.getTextIdentifier()}`
|
||||
case NOTIFICATION_NAMES.LIST_CREATED:
|
||||
return `created ${this.notification.list.title}`
|
||||
case NOTIFICATION_NAMES.TEAM_MEMBER_ADDED:
|
||||
who = `${getDisplayName(this.notification.member)}`
|
||||
|
||||
if (user !== null && user.id === this.notification.member.id) {
|
||||
who = 'you'
|
||||
}
|
||||
|
||||
return `added ${who} to the ${this.notification.team.name} team`
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,14 @@ export function getHexColor(hexColor: string): string {
|
|||
return hexColor
|
||||
}
|
||||
|
||||
export function getTextIdentifier(task: ITask) {
|
||||
if (task.identifier === '') {
|
||||
return `#${task.index}`
|
||||
}
|
||||
|
||||
return task.identifier
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses `repeatAfterSeconds` into a usable js object.
|
||||
*/
|
||||
|
@ -159,11 +167,7 @@ export default class TaskModel extends AbstractModel<ITask> implements ITask {
|
|||
}
|
||||
|
||||
getTextIdentifier() {
|
||||
if (this.identifier === '') {
|
||||
return `#${this.index}`
|
||||
}
|
||||
|
||||
return this.identifier
|
||||
return getTextIdentifier(this)
|
||||
}
|
||||
|
||||
getHexColor() {
|
||||
|
|
Reference in New Issue