+
-
+
{{ card.title }}
@@ -98,6 +102,10 @@ export default {
type: Object,
default: null,
},
+ standalone: {
+ type: Boolean,
+ default: false,
+ },
},
data() {
return {
@@ -114,6 +122,12 @@ export default {
...mapGetters([
'isArchived',
]),
+ board() {
+ return this.$store.getters.boardById(this?.stack?.boardId)
+ },
+ stack() {
+ return this.$store.getters.stackById(this?.card?.stackId)
+ },
canEdit() {
if (this.currentBoard) {
return !this.currentBoard.archived && this.$store.getters.canEdit
@@ -233,6 +247,9 @@ export default {
&.card__editable .card-controls {
margin-right: 0;
}
+ &.card__archived {
+ background-color: var(--color-background-dark);
+ }
}
.duedate {
@@ -244,6 +261,24 @@ export default {
align-items: flex-start;
}
+ .card-related {
+ display: flex;
+ padding: 12px;
+ padding-bottom: 0px;
+ color: var(--color-text-maxcontrast);
+
+ .board-bullet {
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ border: none;
+ border-radius: 50%;
+ background-color: transparent;
+ margin-top: 4px;
+ margin-right: 4px;
+ }
+ }
+
.compact {
min-height: 44px;
diff --git a/src/components/cards/CardMenu.vue b/src/components/cards/CardMenu.vue
index 0517ebea6..36a5e4f6c 100644
--- a/src/components/cards/CardMenu.vue
+++ b/src/components/cards/CardMenu.vue
@@ -23,7 +23,7 @@
-
+
- {{ showArchived ? t('deck', 'Unarchive card') : t('deck', 'Archive card') }}
+ {{ card.archived ? t('deck', 'Unarchive card') : t('deck', 'Archive card') }}
+
+
@@ -82,6 +84,7 @@ import Controls from '../Controls'
import CardItem from '../cards/CardItem'
import { mapGetters } from 'vuex'
import moment from '@nextcloud/moment'
+import GlobalSearchResults from '../search/GlobalSearchResults'
const FILTER_UPCOMING = 'upcoming'
@@ -92,6 +95,7 @@ const SUPPORTED_FILTERS = [
export default {
name: 'Overview',
components: {
+ GlobalSearchResults,
Controls,
CardItem,
},
diff --git a/src/components/search/GlobalSearchResults.vue b/src/components/search/GlobalSearchResults.vue
new file mode 100644
index 000000000..951e9a0d3
--- /dev/null
+++ b/src/components/search/GlobalSearchResults.vue
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('deck', 'No results found') }}
+
+
+
+
+
{{ t('deck', 'No results found') }}
+
+
+
+
+
+
+
+
diff --git a/src/components/search/Placeholder.vue b/src/components/search/Placeholder.vue
new file mode 100644
index 000000000..fa73edd44
--- /dev/null
+++ b/src/components/search/Placeholder.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/store/card.js b/src/store/card.js
index 4f4f4c987..c3c0c39ca 100644
--- a/src/store/card.js
+++ b/src/store/card.js
@@ -21,6 +21,7 @@
*/
import { CardApi } from './../services/CardApi'
+import moment from 'moment'
import Vue from 'vue'
const apiClient = new CardApi()
@@ -86,8 +87,90 @@ export default {
return true
}
- return card.title.toLowerCase().includes(getters.getSearchQuery.toLowerCase())
- || card.description.toLowerCase().includes(getters.getSearchQuery.toLowerCase())
+ let hasMatch = true
+ const matches = getters.getSearchQuery.match(/(?:[^\s"]+|"[^"]*")+/g)
+
+ const filterOutQuotes = (q) => {
+ if (q[0] === '"' && q[q.length - 1] === '"') {
+ return q.substr(1, -1)
+ }
+ return q
+ }
+ for (const match of matches) {
+ let [filter, query] = match.indexOf(':') !== -1 ? match.split(/:(.+)/) : [null, match]
+
+ if (filter === 'title') {
+ hasMatch = hasMatch && card.title.toLowerCase().includes(filterOutQuotes(query).toLowerCase())
+ } else if (filter === 'description') {
+ hasMatch = hasMatch && card.description.toLowerCase().includes(filterOutQuotes(query).toLowerCase())
+ } else if (filter === 'list') {
+ const stack = this.getters.stackById(card.stackId)
+ if (!stack) {
+ return false
+ }
+ hasMatch = hasMatch && stack.title.toLowerCase().includes(filterOutQuotes(query).toLowerCase())
+ } else if (filter === 'tag') {
+ hasMatch = hasMatch && card.labels.findIndex((label) => label.title.toLowerCase().includes(filterOutQuotes(query).toLowerCase())) !== -1
+ } else if (filter === 'date') {
+ const datediffHour = ((new Date(card.duedate) - new Date()) / 3600 / 1000)
+ query = filterOutQuotes(query)
+ switch (query) {
+ case 'overdue':
+ hasMatch = hasMatch && (card.overdue === 3)
+ break
+ case 'today':
+ hasMatch = hasMatch && (datediffHour > 0 && datediffHour <= 24 && card.duedate !== null)
+ break
+ case 'week':
+ hasMatch = hasMatch && (datediffHour > 0 && datediffHour <= 7 * 24 && card.duedate !== null)
+ break
+ case 'month':
+ hasMatch = hasMatch && (datediffHour > 0 && datediffHour <= 30 * 24 && card.duedate !== null)
+ break
+ case 'none':
+ hasMatch = hasMatch && (card.duedate === null)
+ break
+ }
+
+ if (card.duedate === null || !hasMatch) {
+ return false
+ }
+ const comparator = query[0] + (query[1] === '=' ? '=' : '')
+ const isValidComparator = ['<', '<=', '>', '>='].indexOf(comparator) !== -1
+ const parsedCardDate = moment(card.duedate)
+ const parsedDate = moment(query.substr(isValidComparator ? comparator.length : 0))
+ switch (comparator) {
+ case '<':
+ hasMatch = hasMatch && parsedCardDate.isBefore(parsedDate)
+ break
+ case '<=':
+ hasMatch = hasMatch && parsedCardDate.isSameOrBefore(parsedDate)
+ break
+ case '>':
+ hasMatch = hasMatch && parsedCardDate.isAfter(parsedDate)
+ break
+ case '>=':
+ hasMatch = hasMatch && parsedCardDate.isSameOrAfter(parsedDate)
+ break
+ default:
+ hasMatch = hasMatch && parsedCardDate.isSame(parsedDate)
+ break
+ }
+
+ } else if (filter === 'assigned') {
+ hasMatch = hasMatch && card.assignedUsers.findIndex((assignment) => {
+ return assignment.participant.primaryKey.toLowerCase() === filterOutQuotes(query).toLowerCase()
+ || assignment.participant.displayname.toLowerCase() === filterOutQuotes(query).toLowerCase()
+ }) !== -1
+ } else {
+ hasMatch = hasMatch && (card.title.toLowerCase().includes(filterOutQuotes(match).toLowerCase())
+ || card.description.toLowerCase().includes(filterOutQuotes(match).toLowerCase()))
+ }
+ if (!hasMatch) {
+ return false
+ }
+ }
+ return true
})
.sort((a, b) => a.order - b.order || a.createdAt - b.createdAt)
},
@@ -210,7 +293,7 @@ export default {
}
const updatedCard = await apiClient[call](card)
- commit('deleteCard', updatedCard)
+ commit('updateCard', updatedCard)
},
async assignCardToUser({ commit }, { card, assignee }) {
const user = await apiClient.assignUser(card.id, assignee.userId, assignee.type)
@@ -236,5 +319,14 @@ export default {
const updatedCard = await apiClient.updateCard(card)
commit('updateCardProperty', { property: 'duedate', card: updatedCard })
},
+
+ addCardData({ commit }, cardData) {
+ const card = { ...cardData }
+ commit('addStack', card.relatedStack)
+ commit('addBoard', card.relatedBoard)
+ delete card.relatedStack
+ delete card.relatedBoard
+ commit('addCard', card)
+ },
},
}
diff --git a/src/store/main.js b/src/store/main.js
index 6cccd231d..c5562eaf3 100644
--- a/src/store/main.js
+++ b/src/store/main.js
@@ -91,6 +91,9 @@ export default new Vuex.Store({
boards: state => {
return state.boards
},
+ boardById: state => (id) => {
+ return state.boards.find((board) => board.id === id)
+ },
assignables: state => {
return [
...state.assignableUsers.map((user) => ({ ...user, type: 0 })),