diff --git a/website/client/app.vue b/website/client/app.vue index eb1b414414..2d0feaaf89 100644 --- a/website/client/app.vue +++ b/website/client/app.vue @@ -1,12 +1,12 @@ diff --git a/website/client/assets/scss/index.scss b/website/client/assets/scss/index.scss index acd5b2a8ec..4a60ede1b3 100644 --- a/website/client/assets/scss/index.scss +++ b/website/client/assets/scss/index.scss @@ -13,6 +13,7 @@ // Global styles @import './typography'; +@import './markdown'; @import './form'; @import './button'; @import './badge'; diff --git a/website/client/assets/scss/markdown.scss b/website/client/assets/scss/markdown.scss new file mode 100644 index 0000000000..fd6e08d9d7 --- /dev/null +++ b/website/client/assets/scss/markdown.scss @@ -0,0 +1,31 @@ +.markdown { + > p { + margin-bottom: 0px; + } + + h1 { + line-height: 1.17; + } + + h3 { + color: $gray-10; + } + + h1, h2, h3 { + font-weight: normal; + } + + h4 { + font-family: 'Roboto', sans-serif; + font-weight: bold; + } + + a { + color: $blue-10; + + &:hover, &:active, &:focus { + color: $blue-10; + text-decoration: underline; + } + } +} \ No newline at end of file diff --git a/website/client/assets/scss/page.scss b/website/client/assets/scss/page.scss index 7f3bf56847..808d472576 100644 --- a/website/client/assets/scss/page.scss +++ b/website/client/assets/scss/page.scss @@ -1,7 +1,3 @@ -html { - overflow-x: hidden; -} - html, body { height: calc(100% - 56px); // 56px is the menu } diff --git a/website/client/assets/svg/filter.svg b/website/client/assets/svg/filter.svg new file mode 100644 index 0000000000..558ff2f7cc --- /dev/null +++ b/website/client/assets/svg/filter.svg @@ -0,0 +1,3 @@ + + + diff --git a/website/client/components/memberDetails.vue b/website/client/components/memberDetails.vue index 54ba5f6cbb..d80f7e6031 100644 --- a/website/client/components/memberDetails.vue +++ b/website/client/components/memberDetails.vue @@ -120,6 +120,7 @@ display: inline-block; margin-left: 16px; vertical-align: middle; + padding-top: 4px; .svg-icon { display: block; diff --git a/website/client/components/tasks/column.vue b/website/client/components/tasks/column.vue index edec01236e..623ca97d81 100644 --- a/website/client/components/tasks/column.vue +++ b/website/client/components/tasks/column.vue @@ -9,7 +9,11 @@ @click="activeFilter = filter", ) {{ $t(filter.label) }} .tasks-list - task(v-for="task in tasks[`${type}s`]", :key="task.id", :task="task", v-if="activeFilter.filter(task)") + task( + v-for="task in tasks[`${type}s`]", + :key="task.id", :task="task", + v-if="filterTask(task)", + ) .bottom-gradient .column-background(v-if="isUser === true", :class="{'initial-description': tasks[`${type}s`].length === 0}") .svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once) @@ -21,7 +25,7 @@ @import '~client/assets/scss/colors.scss'; .tasks-column { - flex-grow: 1; + height: 556px; } .tasks-list { @@ -136,7 +140,7 @@ export default { components: { Task, }, - props: ['type', 'isUser'], + props: ['type', 'isUser', 'searchText', 'selectedTags'], data () { const types = Object.freeze({ habit: { @@ -192,5 +196,37 @@ export default { userPreferences: 'user.data.preferences', }), }, + methods: { + filterTask (task) { + // View + if (!this.activeFilter.filter(task)) return false; + + // Tags + const selectedTags = this.selectedTags; + + if (selectedTags.length > 0) { + const hasSelectedTag = task.tags.find(tagId => { + return selectedTags.indexOf(tagId) !== -1; + }); + + if (!hasSelectedTag) return false; + } + + // Text + const searchText = this.searchText; + + if (!searchText) return true; + if (task.text.toLowerCase().indexOf(searchText) !== -1) return true; + if (task.notes.toLowerCase().indexOf(searchText) !== -1) return true; + + if (task.checklist && task.checklist.length) { + const checklistItemIndex = task.checklist.findIndex(({text}) => { + return text.toLowerCase().indexOf(searchText) !== -1; + }); + + return checklistItemIndex !== -1; + } + }, + }, }; diff --git a/website/client/components/tasks/task.vue b/website/client/components/tasks/task.vue index f9ef137fd4..7c486c8b04 100644 --- a/website/client/components/tasks/task.vue +++ b/website/client/components/tasks/task.vue @@ -10,11 +10,15 @@ .svg-icon.check(v-html="icons.check", v-if="task.completed") // Task title, description and icons .task-content(:class="contentClass") - h3.task-title( - :class="{ 'has-notes': task.notes }", - v-html="$options.filters.markdown(task.text)" - ) - .task-notes.small-text(v-html="$options.filters.markdown(task.notes)") + h3.task-title(:class="{ 'has-notes': task.notes }", v-markdown="task.text") + .task-notes.small-text(v-markdown="task.notes") + .checklist(v-if="task.checklist && task.checklist.length > 0") + label.custom-control.custom-checkbox.checklist-item( + v-for="item in task.checklist", :class="{'checklist-item-done': item.completed}", + ) + input.custom-control-input(type="checkbox", :checked="item.completed") + span.custom-control-indicator + span.custom-control-description {{ item.text }} .icons.small-text.d-flex.align-items-center .d-flex.align-items-center(v-if="task.type === 'todo' && task.date", :class="{'due-overdue': isDueOverdue}") .svg-icon.calendar(v-html="icons.calendar") @@ -75,15 +79,43 @@ .task-notes { color: $gray-100; font-style: normal; - margin-bottom: 4px; } .task-content { padding: 8px; flex-grow: 1; + cursor: pointer; +} + +.checklist { + margin-bottom: 2px; + margin-top: 8px; +} + +.checklist-item { + color: $gray-50; + font-size: 14px; + line-height: 1.43; + margin-bottom: 10px; + min-height: 0px; + + &-done { + color: $gray-300; + text-decoration: line-through; + } + + .custom-control-indicator { + margin-top: -2px; + } + + .custom-control-description { + margin-left: 6px; + padding-top: 0px; + } } .icons { + margin-top: 4px; color: $gray-300; font-style: normal; @@ -237,11 +269,16 @@ import challengeIcon from 'assets/svg/challenge.svg'; import tagsIcon from 'assets/svg/tags.svg'; import checkIcon from 'assets/svg/check.svg'; import bPopover from 'bootstrap-vue/lib/components/popover'; +import markdownDirective from 'client/directives/markdown'; + export default { components: { bPopover, }, + directives: { + markdown: markdownDirective, + }, props: ['task'], data () { return { diff --git a/website/client/components/tasks/user.vue b/website/client/components/tasks/user.vue index 959567aa39..822f0978c5 100644 --- a/website/client/components/tasks/user.vue +++ b/website/client/components/tasks/user.vue @@ -3,13 +3,51 @@ .col-12 .row.tasks-navigation .col-4.offset-4 - input.form-control.input-search(type="text", :placeholder="$t('search')") + .input-group + input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText") + .filter-panel(v-if="isFilterPanelOpen") + .tags-category.d-flex(v-for="tagsType in tagsByType", v-if="tagsType.tags.length > 0", :key="tagsType.key") + .tags-header(v-once) + strong {{ $t(tagsType.key) }} + a.d-block(v-if="tagsType.key === 'tags'", v-once) {{ $t('editTags2') }} + .tags-list.container + .row.no-gutters + .col-6(v-for="tag in tagsType.tags",) + label.custom-control.custom-checkbox + input.custom-control-input( + type="checkbox", + :checked="isTagSelected(tag)", + @change="toggleTag(tag)", + ) + span.custom-control-indicator + span.custom-control-description {{ tag.name }} + + .filter-panel-footer.clearfix + .float-left + a.reset-filters(@click="resetFilters()", v-once) {{ $t('resetFilters') }} + .float-right + a.mr-3.apply-filters(@click="applyFilters()", v-once) {{ $t('applyFilters') }} + a.cancel-filters(@click="closeFilterPanel()", v-once) {{ $t('cancel') }} + span.input-group-btn + button.btn.btn-secondary.filter-button( + type="button", + @click="toggleFilterPanel()", + :class="{open: isFilterPanelOpen}", + ) + .d-flex.align-items-center + span(v-once) {{ $t('filter') }} + .svg-icon.filter-icon(v-html="icons.filter") .col-1.offset-3 button.btn.btn-success(v-once) .svg-icon.positive(v-html="icons.positive") | {{ $t('create') }} .row.tasks-columns - task-column.col-3(v-for="column in columns", :type="column", :key="column", :isUser="true") + task-column.col-3( + v-for="column in columns", + :type="column", :key="column", + :isUser="true", :searchText="searchTextThrottled", + :selectedTags="selectedTags", + ) diff --git a/website/client/directives/markdown.js b/website/client/directives/markdown.js new file mode 100644 index 0000000000..1b50190271 --- /dev/null +++ b/website/client/directives/markdown.js @@ -0,0 +1,8 @@ +import habiticaMarkdown from 'habitica-markdown'; + +export default function markdown (el, {value, oldValue}) { + if (value === oldValue) return; + + el.innerHTML = habiticaMarkdown.render(value); + el.classList.add('markdown'); +} \ No newline at end of file diff --git a/website/client/filters/markdown.js b/website/client/filters/markdown.js deleted file mode 100644 index 0e385c53bc..0000000000 --- a/website/client/filters/markdown.js +++ /dev/null @@ -1,5 +0,0 @@ -import habiticaMarkdown from 'habitica-markdown'; - -export default function markdown (text) { - return habiticaMarkdown.render(text); -} \ No newline at end of file diff --git a/website/client/filters/registerGlobals.js b/website/client/filters/registerGlobals.js index 652b786280..25604dac1a 100644 --- a/website/client/filters/registerGlobals.js +++ b/website/client/filters/registerGlobals.js @@ -2,9 +2,7 @@ import Vue from 'vue'; import round from './round'; import floor from './floor'; import roundBigNumber from './roundBigNumber'; -import markdown from './markdown'; Vue.filter('round', round); Vue.filter('floor', floor); -Vue.filter('roundBigNumber', roundBigNumber); -Vue.filter('markdown', markdown); \ No newline at end of file +Vue.filter('roundBigNumber', roundBigNumber); \ No newline at end of file diff --git a/website/common/locales/en/newClient.json b/website/common/locales/en/newClient.json index d08249400c..2fdbe8079a 100644 --- a/website/common/locales/en/newClient.json +++ b/website/common/locales/en/newClient.json @@ -186,6 +186,9 @@ "messages": "Messages", "emptyMessagesLine1": "You don’t have any messages", "emptyMessagesLine2": "Send a message to start a conversation!", + "resetFilters": "Clear all filters", + "applyFilters": "Apply Filters", + "editTags2": "Edit Tags" "contributing": "Contributing", "askAQuestion": "Ask a Question", "myChallenges": "My Challenges",