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 @@
-#app.h-100
+#app
router-view(v-if="!isUserLoggedIn || isStaticPage")
template(v-else)
#loading-screen.h-100.w-100.d-flex.justify-content-center.align-items-center(v-if="!isUserLoaded")
p Loading...
template(v-else)
app-menu
- .container-fluid.h-100
+ .container-fluid
app-header
router-view
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",