diff --git a/app/api/projects.py b/app/api/projects.py index 00212c39..7284460e 100644 --- a/app/api/projects.py +++ b/app/api/projects.py @@ -49,10 +49,10 @@ class ProjectFilter(filters.FilterSet): tag_pattern = re.compile("#[^\s]+") tags = set(re.findall(tag_pattern, value)) - deep_tags = set([t for t in tags if t.startswith("##")]) - project_tags = tags - deep_tags + task_tags = set([t for t in tags if t.startswith("##")]) + project_tags = tags - task_tags - deep_tags = [t.replace("##", "") for t in deep_tags] + task_tags = [t.replace("##", "") for t in task_tags] project_tags = [t.replace("#", "") for t in project_tags] names = re.sub("\s+", " ", re.sub(tag_pattern, "", value)).strip() @@ -63,13 +63,12 @@ class ProjectFilter(filters.FilterSet): name_query = SearchQuery(names, search_type="plain") qs = qs.annotate(n_search=project_name_vec + task_name_vec).filter(n_search=name_query) - if len(deep_tags) > 0: - project_tags_vec = SearchVector("tags") - task_tags_vec = SearchVector(StringAgg("task__tags", delimiter=' ')) - tags_query = SearchQuery(deep_tags[0]) - for t in deep_tags[1:]: + if len(task_tags) > 0: + task_tags_vec = SearchVector("task__tags") + tags_query = SearchQuery(task_tags[0]) + for t in task_tags[1:]: tags_query = tags_query & SearchQuery(t) - qs = qs.annotate(dt_search=project_tags_vec + task_tags_vec).filter(dt_search=tags_query) + qs = qs.annotate(tt_search=task_tags_vec).filter(tt_search=tags_query) if len(project_tags) > 0: project_tags_vec = SearchVector("tags") diff --git a/app/static/app/js/components/Paginator.jsx b/app/static/app/js/components/Paginator.jsx index 9ab567b5..ec93da3c 100644 --- a/app/static/app/js/components/Paginator.jsx +++ b/app/static/app/js/components/Paginator.jsx @@ -122,6 +122,8 @@ class Paginator extends React.Component { ref={(domNode) => { this.searchInput = domNode}} className="form-control search theme-border-secondary-07" placeholder={_("Search names or #tags")} + spellCheck="false" + autoComplete="false" value={searchText} onKeyDown={this.handleSearchKeyDown} onChange={this.handleSearchChange} /> diff --git a/app/static/app/js/components/ProjectListItem.jsx b/app/static/app/js/components/ProjectListItem.jsx index f495376a..49a50a31 100644 --- a/app/static/app/js/components/ProjectListItem.jsx +++ b/app/static/app/js/components/ProjectListItem.jsx @@ -41,7 +41,9 @@ class ProjectListItem extends React.Component { importing: false, buttons: [], sortKey: "-created_at", - filterTags: [] + filterTags: [], + selectedTags: [], + filterText: "" }; this.sortItems = [{ @@ -90,6 +92,13 @@ class ProjectListItem extends React.Component { if (this.refreshRequest) this.refreshRequest.abort(); } + componentDidUpdate(prevProps, prevState){ + if (prevState.filterText !== this.state.filterText || + prevState.selectedTags.length !== this.state.selectedTags.length){ + if (this.taskList) this.taskList.applyFilter(this.state.filterText, this.state.selectedTags); + } + } + getDefaultUploadState(){ return { uploading: false, @@ -500,7 +509,42 @@ class ProjectListItem extends React.Component { } tagsChanged = (filterTags) => { - this.setState({filterTags}); + this.setState({filterTags, selectedTags: []}); + } + + handleFilterTextChange = e => { + this.setState({filterText: e.target.value}); + } + + toggleTag = t => { + return () => { + if (this.state.selectedTags.indexOf(t) === -1){ + this.setState(update(this.state, { selectedTags: {$push: [t]} })); + }else{ + this.setState({selectedTags: this.state.selectedTags.filter(tag => tag !== t)}); + } + } + } + + selectTag = t => { + if (this.state.selectedTags.indexOf(t) === -1){ + this.setState(update(this.state, { selectedTags: {$push: [t]} })); + } + } + + clearFilter = () => { + this.setState({ + filterText: "", + selectedTags: [] + }); + } + + onOpenFilter = () => { + if (this.state.filterTags.length === 0){ + setTimeout(() => { + this.filterTextInput.focus(); + }, 0); + } } render() { @@ -585,17 +629,35 @@ class ProjectListItem extends React.Component { {this.state.showTaskList && numTasks > 1 ?
- {filterTags.length > 0 ? -
- - - -
- : ""} +
+ {this.state.selectedTags.length || this.state.filterText !== "" ? + × + : ""} + + + +
{name} {userTags.length > 0 ? - userTags.map((t, i) =>
{t}
) + userTags.map((t, i) =>
{t}
) : ""}
diff --git a/app/static/app/js/css/ProjectListItem.scss b/app/static/app/js/css/ProjectListItem.scss index 77b7e0bf..80041767 100644 --- a/app/static/app/js/css/ProjectListItem.scss +++ b/app/static/app/js/css/ProjectListItem.scss @@ -123,4 +123,49 @@ cursor: pointer; } } + + .filter-dropdown{ + max-width: 320px; + padding-bottom: 6px; + } + + .filter-text{ + height: 25px; + margin-left: 7px; + margin-right: 6px; + margin-bottom: 4px; + padding-left: 4px; + padding-right: 4px; + border-width: 1px; + border-radius: 3px; + display: block; + width: 100%; + } + + .filter-text-container,.tag-selection{ + display: flex; + } + + .filter-checkbox{ + margin-left: 8px; + } + .filter-checkbox-label{ + font-weight: normal; + position: relative; + top: 4px; + overflow: hidden; + text-overflow: ellipsis; + margin-left: 4px; + width: 100%; + } + + .clear-container{ + text-align: right; + margin-top: 2px; + margin-right: 6px; + } + + .quick-clear-filter{ + margin-right: 6px !important; + } }