Fixes, some refactoring

pull/1297/head
Piero Toffanin 2023-03-13 15:15:02 -04:00
rodzic c852f72b20
commit c90b575850
9 zmienionych plików z 123 dodań i 60 usunięć

Wyświetl plik

@ -49,11 +49,11 @@ class ProjectFilter(filters.FilterSet):
tag_pattern = re.compile("#[^\s]+")
tags = set(re.findall(tag_pattern, value))
project_tags = set([t for t in tags if t.startswith("##")])
deep_tags = tags - project_tags
deep_tags = set([t for t in tags if t.startswith("##")])
project_tags = tags - deep_tags
project_tags = [t.replace("##", "") for t in project_tags]
deep_tags = [t.replace("#", "") for t in deep_tags]
deep_tags = [t.replace("##", "") for t in deep_tags]
project_tags = [t.replace("#", "") for t in project_tags]
names = re.sub("\s+", " ", re.sub(tag_pattern, "", value)).strip()

Wyświetl plik

@ -32,24 +32,12 @@ class Paginator extends React.Component {
}];
}
stop = e => {
e.stopPropagation();
}
componentDidMount(){
this.searchPopup.addEventListener("click", this.stop);
this.searchButton.addEventListener("click", this.toggleSearch);
this.btnSearch.addEventListener("click", this.search);
document.body.addEventListener("click", this.closeSearch);
document.addEventListener("onProjectListTagClicked", this.addTagAndSearch);
}
componentWillUnmount(){
document.removeEventListener("onProjectListTagClicked", this.addTagAndSearch);
document.body.removeEventListener("click", this.closeSearch);
this.btnSearch.removeEventListener("click", this.search);
this.searchButton.removeEventListener("click", this.toggleSearch);
this.searchPopup.removeEventListener("click", this.stop);
}
closeSearch = () => {
@ -58,11 +46,9 @@ class Paginator extends React.Component {
toggleSearch = e => {
e.stopPropagation();
this.searchContainer.classList.toggle("open");
setTimeout(() => {
this.searchInput.focus();
}, 0);
}, 50);
}
handleSearchChange = e => {
@ -107,8 +93,8 @@ class Paginator extends React.Component {
if (tag === undefined) return;
let { searchText } = this.state;
if (searchText === "") searchText += "##" + tag;
else searchText += " ##" + tag;
if (searchText === "") searchText += "#" + tag;
else searchText += " #" + tag;
this.setState({searchText});
setTimeout(() => {
@ -122,31 +108,32 @@ class Paginator extends React.Component {
let paginator = null;
let clearSearch = null;
let toolbar = (<ul className="pagination pagination-sm toolbar">
<li className="btn-group" ref={domNode => { this.searchContainer = domNode; }}>
<a href="javascript:void(0);" className="dropdown-toggle"
aria-haspopup="true" aria-expanded="false"
ref={domNode => { this.searchButton = domNode; }} title={_("Search")}><i className="fa fa-search"></i></a>
<ul className="dropdown-menu dropdown-menu-right search-popup" ref={domNode => { this.searchPopup = domNode; }}>
<li>
<input type="text"
ref={(domNode) => { this.searchInput = domNode}}
className="form-control search theme-border-secondary-07"
placeholder={_("Search names or #tags")}
value={searchText}
onKeyDown={this.handleSearchKeyDown}
onChange={this.handleSearchChange} />
<button ref={domNode => {this.btnSearch = domNode;}} className="btn btn-sm btn-default"><i className="fa fa-search"></i></button>
</li>
</ul>
</li>
<li className="btn-group">
<a href="javascript:void(0);" className="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i className="fa fa-sort-alpha-down" title={_("Sort")}></i></a>
<SortPanel selected={this.state.sortKey} items={this.sortItems} onChange={this.sortChanged} />
</li>
</ul>
);
let toolbar = (<ul className={"pagination pagination-sm toolbar " + (totalItems == 0 && !searchText ? "hidden " : " ") + (totalItems / itemsPerPage <= 1 ? "no-margin" : "")}>
<li className="btn-group" ref={domNode => { this.searchContainer = domNode; }}>
<a href="javascript:void(0);" className="dropdown-toggle"
data-toggle-outside
data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"
onClick={this.toggleSearch}
title={_("Search")}><i className="fa fa-search"></i></a>
<ul className="dropdown-menu dropdown-menu-right search-popup">
<li>
<input type="text"
ref={(domNode) => { this.searchInput = domNode}}
className="form-control search theme-border-secondary-07"
placeholder={_("Search names or #tags")}
value={searchText}
onKeyDown={this.handleSearchKeyDown}
onChange={this.handleSearchChange} />
<button onClick={this.search} className="btn btn-sm btn-default"><i className="fa fa-search"></i></button>
</li>
</ul>
</li>
<li className="btn-group">
<a href="javascript:void(0);" className="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i className="fa fa-sort-alpha-down" title={_("Sort")}></i></a>
<SortPanel selected={this.state.sortKey} items={this.sortItems} onChange={this.sortChanged} />
</li>
</ul>);
if (this.props.currentSearch){
let currentSearch = decodeSearch(this.props.currentSearch);

Wyświetl plik

@ -8,6 +8,7 @@ import Paginator from './Paginator';
import ErrorMessage from './ErrorMessage';
import { _, interpolate } from '../classes/gettext';
import PropTypes from 'prop-types';
import Utils from '../classes/Utils';
class ProjectList extends Paginated {
static propTypes = {
@ -33,8 +34,23 @@ class ProjectList extends Paginated {
this.refresh();
}
getParametersHash(source){
if (!source) return "";
if (source.indexOf("?") === -1) return "";
let search = source.substr(source.indexOf("?"));
let q = Utils.queryParams({search});
// All parameters that can change via history.push without
// triggering a reload of the project list should go here
delete q.project_task_open;
delete q.project_task_expanded;
return JSON.stringify(q);
}
componentDidUpdate(prevProps){
if (prevProps.source !== this.props.source){
if (this.getParametersHash(prevProps.source) !== this.getParametersHash(this.props.source)){
this.refresh();
}
}

Wyświetl plik

@ -40,7 +40,8 @@ class ProjectListItem extends React.Component {
refreshing: false,
importing: false,
buttons: [],
sortKey: "-created_at"
sortKey: "-created_at",
filterTags: []
};
this.sortItems = [{
@ -498,8 +499,12 @@ class ProjectListItem extends React.Component {
}
}
tagsChanged = (filterTags) => {
this.setState({filterTags});
}
render() {
const { refreshing, data } = this.state;
const { refreshing, data, filterTags } = this.state;
const numTasks = data.tasks.length;
const canEdit = this.hasPermission("change");
const userTags = Tags.userTags(data.tags);
@ -580,10 +585,17 @@ class ProjectListItem extends React.Component {
{this.state.showTaskList && numTasks > 1 ?
<div className="task-filters">
<i className='fa fa-filter'></i>
<a href="javascript:void(0);" onClick={this.toggleTaskList}>
{_("Filter")}
</a>
{filterTags.length > 0 ?
<div className="btn-group">
<i className='fa fa-filter'></i>
<a href="javascript:void(0);" className="dropdown-toggle" data-toggle-outside data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{_("Filter")}
</a>
<ul className="dropdown-menu dropdown-menu-right">
{filterTags.map(t => <li key={t}>{t}</li>)}
</ul>
</div>
: ""}
<div className="btn-group">
<i className='fa fa-sort-alpha-down'></i>
<a href="javascript:void(0);" className="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@ -645,6 +657,7 @@ class ProjectListItem extends React.Component {
onDelete={this.taskDeleted}
onTaskMoved={this.taskMoved}
hasPermission={this.hasPermission}
onTagsChanged={this.tagsChanged}
history={this.props.history}
/> : ""}

Wyświetl plik

@ -11,7 +11,8 @@ class TaskList extends React.Component {
source: PropTypes.string.isRequired, // URL where to load task list
onDelete: PropTypes.func,
onTaskMoved: PropTypes.func,
hasPermission: PropTypes.func.isRequired
hasPermission: PropTypes.func.isRequired,
onTagsChanged: PropTypes.func
}
constructor(props){
@ -49,6 +50,7 @@ class TaskList extends React.Component {
this.setState({
tasks: json
});
setTimeout(() => this.notifyTagsChanged(), 0);
})
.fail((jqXHR, textStatus, errorThrown) => {
this.setState({
@ -78,6 +80,38 @@ class TaskList extends React.Component {
if (this.props.onTaskMoved) this.props.onTaskMoved(task);
}
notifyTagsChanged = () => {
const { tasks } = this.state;
const tags = [];
if (tasks){
tasks.forEach(t => {
if (t.tags){
t.tags.forEach(x => {
if (tags.indexOf(x) === -1) tags.push(x);
});
}
});
}
tags.sort();
if (this.props.onTagsChanged) this.props.onTagsChanged(tags);
}
taskEdited = (task) => {
// Update
const { tasks } = this.state;
for (let i = 0; i < tasks.length; i++){
if (tasks[i].id === task.id){
tasks[i] = task;
break;
}
}
this.setState({tasks});
// Tags might have changed
setTimeout(() => this.notifyTagsChanged(), 0);
}
render() {
let message = "";
if (this.state.loading){
@ -98,6 +132,7 @@ class TaskList extends React.Component {
onDelete={this.deleteTask}
onMove={this.moveTask}
onDuplicate={this.refresh}
onEdited={this.taskEdited}
hasPermission={this.props.hasPermission}
history={this.props.history} />
))}

Wyświetl plik

@ -24,7 +24,8 @@ class TaskListItem extends React.Component {
onDelete: PropTypes.func,
onMove: PropTypes.func,
onDuplicate: PropTypes.func,
hasPermission: PropTypes.func
hasPermission: PropTypes.func,
onEdited: PropTypes.func
}
constructor(props){
@ -279,6 +280,7 @@ class TaskListItem extends React.Component {
handleEditTaskSave(task){
this.setState({task, editing: false});
if (this.props.onEdited) this.props.onEdited(task);
this.setAutoRefresh();
}

Wyświetl plik

@ -8,6 +8,9 @@
opacity: 0.8;
}
margin-right: 8px;
&.no-margin{
margin-right: 0;
}
}
.btn-group.open > .dropdown-menu{
top: 22px;

Wyświetl plik

@ -1,5 +1,5 @@
/*!
* Bootstrap v3.3.1 (http://getbootstrap.com)
* Bootstrap v3.3.1 (http://getbootstrap.com) modified to allow "data-toggle-outside"
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
@ -838,6 +838,15 @@ if (typeof jQuery === 'undefined') {
if (!$parent.hasClass('open')) return
// Modification to allow toggling only with click outside
if ($this.attr('data-toggle-outside')){
if (e && e.target){
var sibiling = $this.get(0).nextSibling;
if (sibiling === e.target || sibiling.contains(e.target)) return
}
}
// End modification
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
if (e.isDefaultPrevented()) return

File diff suppressed because one or more lines are too long