Compare commits
5 Commits
74ed6516b4
...
fea33eb62e
Author | SHA1 | Date | |
---|---|---|---|
fea33eb62e | |||
be682f4754 | |||
83ae35006b | |||
1bc94bfd0a | |||
547a65e19f |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,2 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.db
|
*.db*
|
||||||
|
10
src/tt/constants.py
Normal file
10
src/tt/constants.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
SPECIAL_DATES_PIECES = {
|
||||||
|
"future": (3000,1,1),
|
||||||
|
"unclassified": (1999,1,1),
|
||||||
|
}
|
||||||
|
SPECIAL_DATES_DISPLAY = {
|
||||||
|
"3000-01-01": "[#333333]future[/]",
|
||||||
|
"1999-01-01": "[#cccccc]unclassified[/]",
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ from peewee import fn
|
|||||||
from peewee import Case, Value
|
from peewee import Case, Value
|
||||||
from ..db import db, Task, SavedSearch
|
from ..db import db, Task, SavedSearch
|
||||||
from .. import config
|
from .. import config
|
||||||
|
from ..constants import SPECIAL_DATES_PIECES
|
||||||
|
|
||||||
|
|
||||||
def get_task(item_id: int) -> Task:
|
def get_task(item_id: int) -> Task:
|
||||||
@ -14,7 +14,7 @@ def get_task(item_id: int) -> Task:
|
|||||||
def add_task(
|
def add_task(
|
||||||
text: str,
|
text: str,
|
||||||
status: str,
|
status: str,
|
||||||
due: datetime | None = None,
|
due: datetime | str = SPECIAL_DATES_PIECES["unclassified"],
|
||||||
type: str = "",
|
type: str = "",
|
||||||
project: str = "",
|
project: str = "",
|
||||||
) -> Task:
|
) -> Task:
|
||||||
@ -84,8 +84,8 @@ def get_tasks(
|
|||||||
query = query.where(fn.Lower(Task.text).contains(search_text.lower()))
|
query = query.where(fn.Lower(Task.text).contains(search_text.lower()))
|
||||||
if statuses:
|
if statuses:
|
||||||
query = query.where(Task.status.in_(statuses))
|
query = query.where(Task.status.in_(statuses))
|
||||||
#if projects:
|
if projects:
|
||||||
# query = query.where(Task.project.in_(projects))
|
query = query.where(Task.project.in_(projects))
|
||||||
|
|
||||||
sort_expressions = _parse_sort_string(sort, statuses)
|
sort_expressions = _parse_sort_string(sort, statuses)
|
||||||
query = query.order_by(*sort_expressions)
|
query = query.order_by(*sort_expressions)
|
||||||
|
89
src/tt/tui/columns.py
Normal file
89
src/tt/tui/columns.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import datetime
|
||||||
|
from ..utils import (
|
||||||
|
get_color_enum,
|
||||||
|
get_colored_date,
|
||||||
|
)
|
||||||
|
from .modals import ChoiceModal, DateModal
|
||||||
|
|
||||||
|
class NotifyValidationError(Exception):
|
||||||
|
"""will notify and continue if raised"""
|
||||||
|
|
||||||
|
|
||||||
|
ELLIPSIS = "…"
|
||||||
|
|
||||||
|
|
||||||
|
class TableColumnConfig:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
field: str,
|
||||||
|
display_name: str,
|
||||||
|
*,
|
||||||
|
default=None,
|
||||||
|
enable_editor=False,
|
||||||
|
filterable=True,
|
||||||
|
read_only=False,
|
||||||
|
):
|
||||||
|
self.field = field
|
||||||
|
self.display_name = display_name
|
||||||
|
self.default = default
|
||||||
|
self.enable_editor = enable_editor
|
||||||
|
self.filterable = filterable
|
||||||
|
self.read_only = read_only
|
||||||
|
|
||||||
|
def preprocess(self, val):
|
||||||
|
return val # no-op
|
||||||
|
|
||||||
|
def start_change(self, app, current_value):
|
||||||
|
if current_value.endswith(ELLIPSIS):
|
||||||
|
app.action_start_edit()
|
||||||
|
else:
|
||||||
|
# default edit mode
|
||||||
|
app._show_input("edit", current_value)
|
||||||
|
|
||||||
|
def format_for_display(self, val):
|
||||||
|
val = str(val)
|
||||||
|
if "\n" in val:
|
||||||
|
val = val.split("\n")[0] + ELLIPSIS
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
class EnumColumnConfig(TableColumnConfig):
|
||||||
|
def __init__(self, field: str, display_name: str, enum, **kwargs):
|
||||||
|
super().__init__(field, display_name, **kwargs)
|
||||||
|
self.enum = enum
|
||||||
|
|
||||||
|
def preprocess(self, val):
|
||||||
|
if val in self.enum:
|
||||||
|
return val
|
||||||
|
else:
|
||||||
|
raise NotifyValidationError(f"Invalid value {val}. Use: {list(self.enum)}")
|
||||||
|
|
||||||
|
def format_for_display(self, val):
|
||||||
|
return get_color_enum(val, self.enum)
|
||||||
|
|
||||||
|
def start_change(self, app, current_value):
|
||||||
|
# a weird hack? pass app here and correct modal gets pushed
|
||||||
|
app.push_screen(ChoiceModal(self.enum, current_value), app.apply_change)
|
||||||
|
|
||||||
|
|
||||||
|
class DateColumnConfig(TableColumnConfig):
|
||||||
|
def preprocess(self, val):
|
||||||
|
try:
|
||||||
|
return datetime.datetime.strptime(val, "%Y-%m-%d")
|
||||||
|
except ValueError:
|
||||||
|
raise NotifyValidationError("Invalid date format. Use YYYY-MM-DD")
|
||||||
|
|
||||||
|
def format_for_display(self, val):
|
||||||
|
return get_colored_date(val)
|
||||||
|
|
||||||
|
def start_change(self, app, current_value):
|
||||||
|
app.push_screen(DateModal(current_value), app.apply_change)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_col_cls(field_type):
|
||||||
|
return {
|
||||||
|
"text": TableColumnConfig,
|
||||||
|
"enum": EnumColumnConfig,
|
||||||
|
"date": DateColumnConfig,
|
||||||
|
}[field_type]
|
@ -1,4 +1,3 @@
|
|||||||
import datetime
|
|
||||||
from textual.app import App
|
from textual.app import App
|
||||||
from textual.widgets import (
|
from textual.widgets import (
|
||||||
DataTable,
|
DataTable,
|
||||||
@ -13,86 +12,10 @@ from ..utils import (
|
|||||||
remove_rich_tag,
|
remove_rich_tag,
|
||||||
filter_to_string,
|
filter_to_string,
|
||||||
get_text_from_editor,
|
get_text_from_editor,
|
||||||
get_color_enum,
|
|
||||||
get_colored_date,
|
|
||||||
)
|
)
|
||||||
from .keymodal import KeyModal
|
from .keymodal import KeyModal
|
||||||
from .modals import ChoiceModal, DateModal, ConfirmModal
|
from .modals import ConfirmModal
|
||||||
|
from .columns import get_col_cls
|
||||||
|
|
||||||
class NotifyValidationError(Exception):
|
|
||||||
"""will notify and continue if raised"""
|
|
||||||
|
|
||||||
|
|
||||||
ELLIPSIS = "…"
|
|
||||||
|
|
||||||
class TableColumnConfig:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
field: str,
|
|
||||||
display_name: str,
|
|
||||||
*,
|
|
||||||
default=None,
|
|
||||||
enable_editor=False,
|
|
||||||
filterable=True,
|
|
||||||
read_only=False,
|
|
||||||
):
|
|
||||||
self.field = field
|
|
||||||
self.display_name = display_name
|
|
||||||
self.default = default
|
|
||||||
self.enable_editor = enable_editor
|
|
||||||
self.filterable = filterable
|
|
||||||
self.read_only = read_only
|
|
||||||
|
|
||||||
def preprocess(self, val):
|
|
||||||
return val # no-op
|
|
||||||
|
|
||||||
def start_change(self, app, current_value):
|
|
||||||
if current_value.endswith(ELLIPSIS):
|
|
||||||
app.action_start_edit()
|
|
||||||
else:
|
|
||||||
# default edit mode
|
|
||||||
app._show_input("edit", current_value)
|
|
||||||
|
|
||||||
def format_for_display(self, val):
|
|
||||||
val = str(val)
|
|
||||||
if "\n" in val:
|
|
||||||
val = val.split("\n")[0] + ELLIPSIS
|
|
||||||
return val
|
|
||||||
|
|
||||||
|
|
||||||
class EnumColumnConfig(TableColumnConfig):
|
|
||||||
def __init__(self, field: str, display_name: str, enum, **kwargs):
|
|
||||||
super().__init__(field, display_name, **kwargs)
|
|
||||||
self.enum = enum
|
|
||||||
|
|
||||||
def preprocess(self, val):
|
|
||||||
if val in self.enum:
|
|
||||||
return val
|
|
||||||
else:
|
|
||||||
raise NotifyValidationError(f"Invalid value {val}. Use: {list(self.enum)}")
|
|
||||||
|
|
||||||
def format_for_display(self, val):
|
|
||||||
return get_color_enum(val, self.enum)
|
|
||||||
|
|
||||||
def start_change(self, app, current_value):
|
|
||||||
# a weird hack? pass app here and correct modal gets pushed
|
|
||||||
app.push_screen(ChoiceModal(self.enum, current_value), app.apply_change)
|
|
||||||
|
|
||||||
|
|
||||||
class DateColumnConfig(TableColumnConfig):
|
|
||||||
def preprocess(self, val):
|
|
||||||
try:
|
|
||||||
return datetime.strptime(val, "%Y-%m-%d")
|
|
||||||
except ValueError:
|
|
||||||
raise NotifyValidationError("Invalid date format. Use YYYY-MM-DD")
|
|
||||||
|
|
||||||
def format_for_display(self, val):
|
|
||||||
return get_colored_date(val)
|
|
||||||
|
|
||||||
def start_change(self, app, current_value):
|
|
||||||
app.push_screen(DateModal(current_value), app.apply_change)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -187,11 +110,7 @@ class TableEditor(App):
|
|||||||
# set up columns
|
# set up columns
|
||||||
for col in view["columns"]:
|
for col in view["columns"]:
|
||||||
field_type = col.get("field_type", "text")
|
field_type = col.get("field_type", "text")
|
||||||
field_cls = {
|
field_cls = get_col_cls(field_type)
|
||||||
"text": TableColumnConfig,
|
|
||||||
"enum": EnumColumnConfig,
|
|
||||||
"date": DateColumnConfig,
|
|
||||||
}[field_type]
|
|
||||||
field_name = col["field_name"]
|
field_name = col["field_name"]
|
||||||
display_name = col.get("display_name", field_name.title())
|
display_name = col.get("display_name", field_name.title())
|
||||||
default = col.get("default")
|
default = col.get("default")
|
||||||
|
@ -37,6 +37,7 @@ class KeyModal(ModalScreen):
|
|||||||
for binding in self.app.BINDINGS:
|
for binding in self.app.BINDINGS:
|
||||||
if binding[0] not in ["h", "j", "k", "l", "g", "G", "escape"]:
|
if binding[0] not in ["h", "j", "k", "l", "g", "G", "escape"]:
|
||||||
table.add_row(binding[0], binding[2])
|
table.add_row(binding[0], binding[2])
|
||||||
|
# TODO: MRO?
|
||||||
|
|
||||||
yield Static("tt keybindings", classes="title")
|
yield Static("tt keybindings", classes="title")
|
||||||
yield Static(table)
|
yield Static(table)
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
|
import datetime
|
||||||
from textual.screen import ModalScreen
|
from textual.screen import ModalScreen
|
||||||
from textual.binding import Binding
|
from textual.binding import Binding
|
||||||
from textual.widgets import RadioSet, RadioButton, Label
|
from textual.widgets import Label
|
||||||
from .. import config
|
from textual.containers import Horizontal, Vertical
|
||||||
|
from textual.reactive import reactive
|
||||||
from ..utils import get_color_enum
|
from ..utils import get_color_enum
|
||||||
|
from ..constants import SPECIAL_DATES_PIECES
|
||||||
|
|
||||||
|
|
||||||
class ConfirmModal(ModalScreen):
|
class ConfirmModal(ModalScreen):
|
||||||
CSS = """
|
CSS = """
|
||||||
@ -42,42 +46,56 @@ class ChoiceModal(ModalScreen):
|
|||||||
ChoiceModal Label {
|
ChoiceModal Label {
|
||||||
height: 1;
|
height: 1;
|
||||||
}
|
}
|
||||||
|
ChoiceModal Label#selected {
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
("j,tab", "cursor_down", "Down"),
|
("j,tab", "cursor_down", "Down"),
|
||||||
("k", "cursor_up", "Up"),
|
("k,shift+tab", "cursor_up", "Up"),
|
||||||
Binding("enter", "select", "Select", priority=False),
|
Binding("enter", "select", "Select", priority=True),
|
||||||
("escape", "cancel", "cancel"),
|
("escape", "cancel", "cancel"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, enum, selected):
|
def __init__(self, enum, selected):
|
||||||
self._enum = enum
|
self._enum = enum
|
||||||
self.selected = selected
|
self.enum_by_idx = list(self._enum)
|
||||||
|
# selection index
|
||||||
|
self.sel_idx = 0
|
||||||
|
# convert value back to index for initial selection
|
||||||
|
for idx, e in enumerate(self._enum):
|
||||||
|
if e.value == selected:
|
||||||
|
self.sel_idx = idx
|
||||||
|
break
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield RadioSet(
|
for idx, e in enumerate(self._enum):
|
||||||
*[
|
yield Label(
|
||||||
RadioButton(
|
("> " if idx == self.sel_idx else " ") +
|
||||||
get_color_enum(e.value, config.STATUSES), value=self.selected == str(e.value)
|
get_color_enum(e.value, self._enum),
|
||||||
)
|
classes="selected" if idx == self.sel_idx else "",
|
||||||
for e in self._enum
|
)
|
||||||
]
|
|
||||||
)
|
def _move_cursor(self, dir):
|
||||||
|
labels = self.query(Label)
|
||||||
|
# reset old
|
||||||
|
labels[self.sel_idx].update(" " + get_color_enum(self.enum_by_idx[self.sel_idx], self._enum))
|
||||||
|
# move cursor
|
||||||
|
self.sel_idx = (self.sel_idx + dir) % len(self._enum)
|
||||||
|
# reset new
|
||||||
|
labels[self.sel_idx].update("> " + get_color_enum(self.enum_by_idx[self.sel_idx], self._enum))
|
||||||
|
|
||||||
def action_cursor_down(self):
|
def action_cursor_down(self):
|
||||||
self.query_one(RadioSet).action_next_button()
|
self._move_cursor(1)
|
||||||
|
|
||||||
def action_cursor_up(self):
|
def action_cursor_up(self):
|
||||||
self.query_one(RadioSet).action_previous_button()
|
self._move_cursor(-1)
|
||||||
|
|
||||||
def action_select(self):
|
async def action_select(self):
|
||||||
rs = self.query_one(RadioSet)
|
self.dismiss(self.enum_by_idx[self.sel_idx])
|
||||||
# TODO: this doesn't work
|
|
||||||
#rs.action_toggle_button()
|
|
||||||
pressed = rs.pressed_button
|
|
||||||
self.dismiss(str(pressed.label))
|
|
||||||
|
|
||||||
def action_cancel(self):
|
def action_cancel(self):
|
||||||
self.app.pop_screen()
|
self.app.pop_screen()
|
||||||
@ -86,37 +104,71 @@ class ChoiceModal(ModalScreen):
|
|||||||
class DateModal(ModalScreen):
|
class DateModal(ModalScreen):
|
||||||
CSS = """
|
CSS = """
|
||||||
DateModal {
|
DateModal {
|
||||||
layout: horizontal;
|
|
||||||
align: center middle;
|
align: center middle;
|
||||||
background: $primary 30%;
|
background: $primary 30%;
|
||||||
}
|
}
|
||||||
|
DateModal Vertical {
|
||||||
|
border: double teal;
|
||||||
|
height: 10;
|
||||||
|
width: 50;
|
||||||
|
}
|
||||||
|
DateModal Horizonal {
|
||||||
|
}
|
||||||
DateModal Label {
|
DateModal Label {
|
||||||
border: solid grey;
|
border: solid white;
|
||||||
|
align: center middle;
|
||||||
}
|
}
|
||||||
DateModal Label.selected-date {
|
DateModal Label.selected-date {
|
||||||
border: solid green;
|
border: solid green;
|
||||||
}
|
}
|
||||||
|
DateModal Label.hints {
|
||||||
|
border: solid grey;
|
||||||
|
height: 4;
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
("j", "cursor_down", "Down"),
|
("j", "cursor_down", "Down"),
|
||||||
("k", "cursor_up", "Up"),
|
("k", "cursor_up", "Up"),
|
||||||
("h,tab", "cursor_left", "Left"),
|
("h,shift+tab", "cursor_left", "Left"),
|
||||||
("l", "cursor_right", "Right"),
|
("l,tab", "cursor_right", "Right"),
|
||||||
|
("f", "future", "Future"),
|
||||||
|
("t", "today", "Today"),
|
||||||
|
("u", "unclassified", "Unclassified"),
|
||||||
Binding("enter", "select", "Select", priority=True),
|
Binding("enter", "select", "Select", priority=True),
|
||||||
("escape", "cancel", "cancel"),
|
("escape", "cancel", "cancel"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
pieces = reactive([0, 0, 0], recompose=True)
|
||||||
|
|
||||||
def __init__(self, date):
|
def __init__(self, date):
|
||||||
self.pieces = [int(p) for p in date.split("-")]
|
|
||||||
self.selected = 1 # start on month
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
if date in SPECIAL_DATES_PIECES:
|
||||||
|
self.pieces = list(SPECIAL_DATES_PIECES[date])
|
||||||
|
elif date:
|
||||||
|
self.pieces = [int(p) for p in date.split("-")]
|
||||||
|
else:
|
||||||
|
self.action_today()
|
||||||
|
self.selected = 1 # start on month
|
||||||
|
|
||||||
def compose(self):
|
def compose(self):
|
||||||
for idx, piece in enumerate(self.pieces):
|
with Vertical():
|
||||||
yield Label(
|
with Horizontal():
|
||||||
str(piece), classes="selected-date" if idx == self.selected else ""
|
yield Label(f"{self.pieces[0]}")
|
||||||
)
|
yield Label(f"{self.pieces[1]}", classes="selected-date")
|
||||||
|
yield Label(f"{self.pieces[2]}")
|
||||||
|
yield Label("""(h/j/k/l) move (enter) confirm (esc) quit
|
||||||
|
(p)ast (t)oday (f)uture""", classes="hints")
|
||||||
|
|
||||||
|
def action_future(self):
|
||||||
|
self.pieces = list(SPECIAL_DATES_PIECES["future"])
|
||||||
|
|
||||||
|
def action_unclassified(self):
|
||||||
|
self.pieces = list(SPECIAL_DATES_PIECES["unclassified"])
|
||||||
|
|
||||||
|
def action_today(self):
|
||||||
|
today = datetime.date.today()
|
||||||
|
self.pieces = [today.year, today.month, today.day]
|
||||||
|
|
||||||
def action_cursor_left(self):
|
def action_cursor_left(self):
|
||||||
# cycle Y/M/D
|
# cycle Y/M/D
|
||||||
@ -152,8 +204,7 @@ class DateModal(ModalScreen):
|
|||||||
if cur_value > self.max_for(self.selected):
|
if cur_value > self.max_for(self.selected):
|
||||||
cur_value = 1
|
cur_value = 1
|
||||||
self.pieces[self.selected] = cur_value
|
self.pieces[self.selected] = cur_value
|
||||||
cur_label = self.query("Label")[self.selected]
|
self.mutate_reactive(DateModal.pieces)
|
||||||
cur_label.update(str(cur_value))
|
|
||||||
|
|
||||||
def action_cursor_down(self):
|
def action_cursor_down(self):
|
||||||
self._move_piece(-1)
|
self._move_piece(-1)
|
||||||
@ -178,8 +229,7 @@ class DateModal(ModalScreen):
|
|||||||
event.prevent_default()
|
event.prevent_default()
|
||||||
|
|
||||||
def action_select(self):
|
def action_select(self):
|
||||||
date = "-".join(str(p) for p in self.pieces)
|
self.dismiss("-".join(str(p) for p in self.pieces))
|
||||||
self.dismiss(date)
|
|
||||||
|
|
||||||
def action_cancel(self):
|
def action_cancel(self):
|
||||||
self.app.pop_screen()
|
self.app.pop_screen()
|
||||||
|
@ -56,10 +56,8 @@ class TT(TableEditor):
|
|||||||
def refresh_items(self):
|
def refresh_items(self):
|
||||||
items = get_tasks(
|
items = get_tasks(
|
||||||
self.search_query,
|
self.search_query,
|
||||||
projects=self.filters.get("project", "").split(","),
|
projects=self._filters_to_list("project"),
|
||||||
statuses=self.filters.get("status", "").split(",")
|
statuses=self._filters_to_list("status"),
|
||||||
if "status" in self.filters
|
|
||||||
else None,
|
|
||||||
sort=self.sort_string,
|
sort=self.sort_string,
|
||||||
)
|
)
|
||||||
for item in items:
|
for item in items:
|
||||||
@ -68,6 +66,12 @@ class TT(TableEditor):
|
|||||||
key=str(item.id),
|
key=str(item.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _filters_to_list(self, key):
|
||||||
|
filters = self.filters.get(key)
|
||||||
|
if filters:
|
||||||
|
return filters.split(",")
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def run(default_view):
|
def run(default_view):
|
||||||
app = TT(default_view)
|
app = TT(default_view)
|
||||||
|
@ -3,6 +3,7 @@ import os
|
|||||||
import datetime
|
import datetime
|
||||||
import tempfile
|
import tempfile
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from .constants import SPECIAL_DATES_DISPLAY
|
||||||
|
|
||||||
|
|
||||||
def filter_to_string(filters, search_query):
|
def filter_to_string(filters, search_query):
|
||||||
@ -41,6 +42,8 @@ def get_colored_date(date: datetime.date) -> str:
|
|||||||
if not isinstance(date, datetime.date):
|
if not isinstance(date, datetime.date):
|
||||||
return ""
|
return ""
|
||||||
as_str = date.strftime("%Y-%m-%d")
|
as_str = date.strftime("%Y-%m-%d")
|
||||||
|
if as_str in SPECIAL_DATES_DISPLAY:
|
||||||
|
return SPECIAL_DATES_DISPLAY[as_str]
|
||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
if date.date() < today:
|
if date.date() < today:
|
||||||
return f"[#eeeeee on #dd1111]{as_str}[/]"
|
return f"[#eeeeee on #dd1111]{as_str}[/]"
|
||||||
|
6
tt.toml
6
tt.toml
@ -28,7 +28,7 @@ values = [
|
|||||||
|
|
||||||
[[views]]
|
[[views]]
|
||||||
name = "tasks"
|
name = "tasks"
|
||||||
sort = "due"
|
sort = "due,status"
|
||||||
|
|
||||||
[views.filters]
|
[views.filters]
|
||||||
status = "wip,blocked,zero"
|
status = "wip,blocked,zero"
|
||||||
@ -40,7 +40,7 @@ read_only = true
|
|||||||
[[views.columns]]
|
[[views.columns]]
|
||||||
field_name = "text"
|
field_name = "text"
|
||||||
display_name = "Task"
|
display_name = "Task"
|
||||||
default = "new taskz"
|
default = "new task"
|
||||||
editor = true
|
editor = true
|
||||||
|
|
||||||
[[views.columns]]
|
[[views.columns]]
|
||||||
@ -56,7 +56,7 @@ default = ""
|
|||||||
[[views.columns]]
|
[[views.columns]]
|
||||||
field_name = "due"
|
field_name = "due"
|
||||||
field_type = "date"
|
field_type = "date"
|
||||||
default = ""
|
default = "1999-01-01"
|
||||||
|
|
||||||
[[views.columns]]
|
[[views.columns]]
|
||||||
field_name = "project"
|
field_name = "project"
|
||||||
|
Loading…
Reference in New Issue
Block a user