Compare commits
26 Commits
v0.5
...
01adec7db5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01adec7db5 | ||
|
|
95b10fd9f6 | ||
|
|
fc088ec240 | ||
|
|
2763b68bc4 | ||
|
|
f84614a8da | ||
|
|
c2ed541604 | ||
|
|
8c7fe2e9ef | ||
|
|
9301ddd467 | ||
|
|
d5ec073f75 | ||
|
|
b8f0efc01c | ||
|
|
81c8743de7 | ||
|
|
6abfab229a | ||
|
|
ec780f7d9b | ||
|
|
94a14f8b9d | ||
|
|
90dbfd6f5a | ||
|
|
de5f17e2c9 | ||
|
|
0e22586e12 | ||
|
|
80becbb7a7 | ||
|
|
2e98bdff2d | ||
|
|
8f966c98a4 | ||
|
|
a0f336ca67 | ||
|
|
f8b5eb71e0 | ||
|
|
7c70ba27cb | ||
|
|
683b7a5f2d | ||
|
|
a77f28fbcf | ||
|
|
6291742690 |
@@ -5,16 +5,18 @@ import (
|
||||
"local/args"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
Port string
|
||||
StoreType string
|
||||
StoreAddr string
|
||||
StoreUser string
|
||||
StorePass string
|
||||
Root string
|
||||
MyTinyTodo string
|
||||
Port string
|
||||
StoreType string
|
||||
StoreAddr string
|
||||
StoreUser string
|
||||
StorePass string
|
||||
Root string
|
||||
OAuth string
|
||||
Loop time.Duration
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -32,8 +34,9 @@ func Refresh() {
|
||||
as.Append(args.STRING, "storeaddr", "addr of store", "")
|
||||
as.Append(args.STRING, "storeuser", "user of store", "")
|
||||
as.Append(args.STRING, "storepass", "pass of store", "")
|
||||
as.Append(args.STRING, "mtt", "url of php server", "http://localhost:38808")
|
||||
as.Append(args.STRING, "oauth", "url for boauthz", "")
|
||||
as.Append(args.STRING, "root", "root of static files", "./public")
|
||||
as.Append(args.DURATION, "loop", "loop duration for refreshing completed tasks", time.Minute)
|
||||
if err := as.Parse(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -44,5 +47,6 @@ func Refresh() {
|
||||
StoreUser = as.Get("storeuser").GetString()
|
||||
StorePass = as.Get("storepass").GetString()
|
||||
Root = as.Get("root").GetString()
|
||||
MyTinyTodo = as.Get("mtt").GetString()
|
||||
Loop = as.Get("loop").GetDuration()
|
||||
OAuth = as.Get("oauth").GetString()
|
||||
}
|
||||
|
||||
1
main.go
1
main.go
@@ -12,6 +12,7 @@ func main() {
|
||||
if err := s.Routes(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go s.Async()
|
||||
log.Println("listening on", config.Port)
|
||||
if err := http.ListenAndServe(config.Port, s); err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<title>todo breel</title>
|
||||
<link rel="stylesheet" type="text/css" href="/themes/default/style.css?v=1.4.3" media="all"/>
|
||||
<link rel="stylesheet" type="text/css" href="/themes/default/print.css?v=1.4.3" media="print"/>
|
||||
<link rel="stylesheet" type="text/css" href="/themes/default/print.css?v=1.4.3" media="print"/>
|
||||
<link rel="stylesheet" type="text/css" href="/themes/default/pda.css?v=1.4.3" media="only screen and (max-device-width: 720px)"/>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -166,6 +168,14 @@
|
||||
<span class="h">Due </span>
|
||||
<input name="duedate" id="duedate" value="" class="in100" title="Y-M-D, M/D/Y, D.M.Y, M/D, D.M" autocomplete="off"/>
|
||||
</div>
|
||||
<div class="form-row form-row-short">
|
||||
<span class="h">Loop </span>
|
||||
<input type="text" name="loop" value="" class="in100" maxlength="30"/>
|
||||
</div>
|
||||
<div class="form-row form-row-short">
|
||||
<span class="h">Cron </span>
|
||||
<input type="text" name="cron" value="" class="in100" maxlength="30"/>
|
||||
</div>
|
||||
<div class="form-row-short-end"></div>
|
||||
<div class="form-row">
|
||||
<div class="h">Task</div>
|
||||
@@ -255,6 +265,8 @@
|
||||
<li class="mtt-menu-delimiter"></li>
|
||||
<li class="mtt-need-list" id="btnShowCompleted">
|
||||
<div class="menu-icon"></div>Show completed tasks</li>
|
||||
<li class="mtt-need-list" id="btnShowLooping">
|
||||
<div class="menu-icon"></div>Show looping tasks</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ function getGetParamValue(url, paramName) {
|
||||
this._lists = {};
|
||||
this._length = 0;
|
||||
this._order = [];
|
||||
this._alltasks = {id: -1, showCompl: 0, sort: 3};
|
||||
this._alltasks = {id: -1, showCompl: 0, showLooping: 0, sort: 3};
|
||||
},
|
||||
length: function () {
|
||||
return this._length;
|
||||
@@ -851,6 +851,7 @@ function getGetParamValue(url, paramName) {
|
||||
_mtt.db.request('loadTasks', {
|
||||
list: curList.id,
|
||||
compl: curList.showCompl,
|
||||
looping: curList.showLooping,
|
||||
sort: curList.sort,
|
||||
search: filter.search,
|
||||
tag: _mtt.filter.getTags(true),
|
||||
@@ -1233,6 +1234,9 @@ function getGetParamValue(url, paramName) {
|
||||
case 'btnRssFeed':
|
||||
feedCurList();
|
||||
break;
|
||||
case 'btnShowLooping':
|
||||
showLoopingToggle();
|
||||
break;
|
||||
case 'btnShowCompleted':
|
||||
showCompletedToggle();
|
||||
break;
|
||||
@@ -1354,6 +1358,8 @@ function getGetParamValue(url, paramName) {
|
||||
form.tags.value = item.tags.split(',').join(', ');
|
||||
form.duedate.value = item.duedate;
|
||||
form.prio.value = item.prio;
|
||||
form.loop.value = item.loop;
|
||||
form.cron.value = item.cron;
|
||||
$('#taskedit-date .date-created>span').text(item.date);
|
||||
if (item.compl) $('#taskedit-date .date-completed').show().find('span').text(item.dateCompleted);
|
||||
else $('#taskedit-date .date-completed').hide();
|
||||
@@ -1370,6 +1376,8 @@ function getGetParamValue(url, paramName) {
|
||||
form.duedate.value = '';
|
||||
form.prio.value = '0';
|
||||
form.id.value = '';
|
||||
form.loop.value = '';
|
||||
form.cron.value = '';
|
||||
toggleEditAllTags(0);
|
||||
}
|
||||
|
||||
@@ -1408,11 +1416,17 @@ function getGetParamValue(url, paramName) {
|
||||
if (flag.readOnly) return false;
|
||||
if (form.isadd.value != 0)
|
||||
return submitFullTask(form);
|
||||
|
||||
_mtt.db.request('editTask', {
|
||||
id: form.id.value, title: form.task.value, note: form.note.value,
|
||||
prio: form.prio.value, tags: form.tags.value, duedate: form.duedate.value
|
||||
},
|
||||
var requestBody = {
|
||||
id: form.id.value,
|
||||
title: form.task.value,
|
||||
note: form.note.value,
|
||||
prio: form.prio.value,
|
||||
tags: form.tags.value,
|
||||
duedate: form.duedate.value,
|
||||
loop: form.loop.value,
|
||||
cron: form.cron.value,
|
||||
}
|
||||
_mtt.db.request('editTask', requestBody,
|
||||
function (json) {
|
||||
if (!parseInt(json.total)) return;
|
||||
var item = json.list[0];
|
||||
@@ -1531,7 +1545,9 @@ function getGetParamValue(url, paramName) {
|
||||
note: form.note.value,
|
||||
prio: form.prio.value,
|
||||
tags: form.tags.value,
|
||||
duedate: form.duedate.value
|
||||
duedate: form.duedate.value,
|
||||
loop: form.loop.value,
|
||||
cron: form.cron.value,
|
||||
}, function (json) {
|
||||
if (!parseInt(json.total)) return;
|
||||
form.task.value = '';
|
||||
@@ -1880,6 +1896,8 @@ function getGetParamValue(url, paramName) {
|
||||
}
|
||||
if (list.showCompl) $('#btnShowCompleted').addClass('mtt-item-checked');
|
||||
else $('#btnShowCompleted').removeClass('mtt-item-checked');
|
||||
if (list.showLooping) $('#btnShowLooping').addClass('mtt-item-checked');
|
||||
else $('#btnShowLooping').removeClass('mtt-item-checked');
|
||||
}
|
||||
|
||||
function listOrderChanged(event, ui) {
|
||||
@@ -1893,6 +1911,14 @@ function getGetParamValue(url, paramName) {
|
||||
_mtt.doAction('listOrderChanged', {order: order});
|
||||
}
|
||||
|
||||
function showLoopingToggle() { // todo
|
||||
var act = curList.showLooping ? 0 : 1;
|
||||
curList.showLooping = tabLists.get(curList.id).showLooping = act;
|
||||
if (act) $('#btnShowLooping').addClass('mtt-item-checked');
|
||||
else $('#btnShowLooping').removeClass('mtt-item-checked');
|
||||
loadTasks({setLooping: 1});
|
||||
}
|
||||
|
||||
function showCompletedToggle() {
|
||||
var act = curList.showCompl ? 0 : 1;
|
||||
curList.showCompl = tabLists.get(curList.id).showCompl = act;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
})
|
||||
*/
|
||||
|
||||
$.getJSON(this.mtt.mttUrl + 'ajax.php?loadTasks&list=' + params.list + '&compl=' + params.compl + '&sort=' + params.sort + q, callback);
|
||||
$.getJSON(this.mtt.mttUrl + 'ajax.php?loadTasks&list=' + params.list + '&compl=' + params.compl + '&looping=' + params.looping + '&sort=' + params.sort + q, callback);
|
||||
},
|
||||
|
||||
|
||||
@@ -64,7 +64,9 @@
|
||||
note: params.note,
|
||||
prio: params.prio,
|
||||
tags: params.tags,
|
||||
duedate: params.duedate
|
||||
duedate: params.duedate,
|
||||
loop: params.loop,
|
||||
cron: params.cron,
|
||||
},
|
||||
callback, 'json');
|
||||
},
|
||||
@@ -78,7 +80,9 @@
|
||||
note: params.note,
|
||||
prio: params.prio,
|
||||
tags: params.tags,
|
||||
duedate: params.duedate
|
||||
duedate: params.duedate,
|
||||
loop: params.loop,
|
||||
cron: params.cron,
|
||||
},
|
||||
callback, 'json');
|
||||
},
|
||||
|
||||
@@ -188,3 +188,34 @@ h3 {
|
||||
.mtt-notes-showhide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mtt-tab {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mtt-tabs-selected {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* BEL */
|
||||
|
||||
#taskview ,
|
||||
#bar ,
|
||||
br[clear="all"] ,
|
||||
#task_placeholder > span ,
|
||||
#mtt_body > h2:first-child {
|
||||
display: none !important;
|
||||
}
|
||||
#toolbar ,
|
||||
#taskcontainer {
|
||||
z-index: 15;
|
||||
}
|
||||
#taskcontainer {
|
||||
position: fixed;
|
||||
top: 6em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
|
||||
@@ -605,6 +605,7 @@ li.task-expanded .task-toggle {
|
||||
.task-middle {
|
||||
margin-left: 40px;
|
||||
margin-right: 20px;
|
||||
padding-right: 2.5em;
|
||||
}
|
||||
|
||||
#tasklist {
|
||||
@@ -793,6 +794,7 @@ li:hover a.taskactionbtn, a.taskactionbtn.mtt-menu-button-active {
|
||||
min-height: 16px;
|
||||
display: none;
|
||||
margin: .7em .5em 0 0;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
li.task-expanded .task-note-block {
|
||||
@@ -1366,3 +1368,8 @@ li.mtt-item-hidden {
|
||||
min-width: 350px;
|
||||
}
|
||||
body { filter: invert(80%); background-color: #222; }
|
||||
#newtask_adv ,
|
||||
#tagcloudbtn ,
|
||||
#settings {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@ package ajax
|
||||
import (
|
||||
"local/storage"
|
||||
"local/todo-server/config"
|
||||
"local/todo-server/server/ajax/task"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Ajax struct {
|
||||
@@ -82,3 +85,78 @@ func has(params url.Values, k string) bool {
|
||||
_, ok := params[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (a *Ajax) Async() {
|
||||
var err error
|
||||
nextDue := time.Now().Add(config.Loop)
|
||||
c := time.NewTicker(config.Loop)
|
||||
c2 := time.NewTicker(config.Loop)
|
||||
for {
|
||||
c.Stop()
|
||||
c.Reset(config.Loop)
|
||||
c2.Stop()
|
||||
c2.Reset(time.Until(nextDue))
|
||||
log.Println("next loop at", time.Until(nextDue), "or", config.Loop)
|
||||
select {
|
||||
case <-c.C:
|
||||
case <-c2.C:
|
||||
}
|
||||
nextDue, err = a.loopTasks()
|
||||
if err != nil {
|
||||
log.Println("failed to loop tasks", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Ajax) loopTasks() (time.Time, error) {
|
||||
nextDue := time.Now().Add(time.Hour * 240)
|
||||
lists, err := a.storageListLists()
|
||||
if err != nil {
|
||||
return nextDue, err
|
||||
}
|
||||
for _, list := range lists {
|
||||
tasks, err := a.storageListTasks(list.UUID, func(t *task.Task) bool {
|
||||
return t.Complete
|
||||
})
|
||||
if err != nil {
|
||||
return nextDue, err
|
||||
}
|
||||
for _, task := range tasks {
|
||||
if !task.Complete {
|
||||
continue
|
||||
}
|
||||
if task.Loop == 0 && task.Cron == "" {
|
||||
continue
|
||||
}
|
||||
var nextTask time.Time
|
||||
if task.Loop > 0 {
|
||||
nextTask = task.Completed.Add(task.Loop)
|
||||
}
|
||||
if string(task.Cron) != "" {
|
||||
nextTask2 := task.Cron.Next(task.Completed)
|
||||
if nextTask2 != (time.Time{}) && (nextTask == (time.Time{}) || nextTask2.Before(nextTask)) {
|
||||
nextTask = nextTask2
|
||||
}
|
||||
}
|
||||
if time.Now().After(nextTask) {
|
||||
task.Complete = false
|
||||
task.Completed = time.Time{}
|
||||
if nextTask == (time.Time{}) {
|
||||
task.Title += " !!!INVALID CRON/LOOP!!!"
|
||||
}
|
||||
task.Index = list.NextIndex()
|
||||
if err := a.storageSetTask(list.UUID, task); err != nil {
|
||||
return nextDue, err
|
||||
}
|
||||
if err := a.storageSetList(list); err != nil {
|
||||
return nextDue, err
|
||||
}
|
||||
} else {
|
||||
if nextTask.Before(nextDue) {
|
||||
nextDue = nextTask
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nextDue, nil
|
||||
}
|
||||
|
||||
19
server/ajax/form/cron.go
Normal file
19
server/ajax/form/cron.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
type Cron string
|
||||
|
||||
func (c Cron) Next(since time.Time) time.Time {
|
||||
schedule, err := cron.ParseStandard(string(c))
|
||||
if err != nil {
|
||||
log.Printf("failed to parse cron %q: %v", string(c), err)
|
||||
return time.Time{}
|
||||
}
|
||||
return schedule.Next(since)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -60,6 +61,38 @@ func ToStrArr(k string) []string {
|
||||
return outArr
|
||||
}
|
||||
|
||||
func ToDuration(s string) time.Duration {
|
||||
var d time.Duration
|
||||
for _, c := range []struct {
|
||||
key string
|
||||
d time.Duration
|
||||
}{
|
||||
{"d", time.Hour * 24},
|
||||
{"w", time.Hour * 24 * 7},
|
||||
} {
|
||||
daysPattern := regexp.MustCompile(`(^|[a-z])[0-9]+` + c.key + `($|[0-9])`)
|
||||
idxes := daysPattern.FindAllStringIndex(s, -1)
|
||||
if len(idxes) > 1 {
|
||||
return 0
|
||||
}
|
||||
for _, idx := range idxes {
|
||||
substr := s[idx[0]:idx[1]]
|
||||
for len(substr) > 0 && (substr[0] < '0' || substr[0] > '9') {
|
||||
substr = substr[1:]
|
||||
}
|
||||
for len(substr) > 0 && (substr[len(substr)-1] >= '0' && substr[len(substr)-1] <= '9') {
|
||||
substr = substr[:len(substr)-1]
|
||||
}
|
||||
s = strings.ReplaceAll(s, substr, "")
|
||||
substr = strings.TrimSuffix(substr, c.key)
|
||||
n, _ := strconv.Atoi(substr)
|
||||
d += c.d * time.Duration(n)
|
||||
}
|
||||
}
|
||||
d2, _ := time.ParseDuration(s)
|
||||
return d2 + d
|
||||
}
|
||||
|
||||
func ToTime(s string) time.Time {
|
||||
v, err := time.Parse("2006-01-02 15:04:05", s)
|
||||
if err != nil || v.IsZero() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
@@ -142,3 +143,29 @@ func testReq() *http.Request {
|
||||
"d": "e, f,g"
|
||||
}`))
|
||||
}
|
||||
|
||||
func TestToDuration(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
input string
|
||||
want time.Duration
|
||||
}{
|
||||
"invalid": {},
|
||||
"simple": {input: "1s", want: time.Second},
|
||||
"compound": {input: "1m1s", want: time.Minute + time.Second},
|
||||
"compound:unsorted": {input: "1s1m", want: time.Minute + time.Second},
|
||||
"compound:extension": {input: "1d1m", want: time.Minute + time.Hour*24},
|
||||
"extension:day": {input: "1d", want: 24 * time.Hour},
|
||||
"extension:week": {input: "1w", want: 24 * 7 * time.Hour},
|
||||
"extension:week,day": {input: "1w1d", want: 24 * 8 * time.Hour},
|
||||
}
|
||||
|
||||
for name, d := range cases {
|
||||
c := d
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := ToDuration(c.input)
|
||||
if got != c.want {
|
||||
t.Fatal(c.input, c.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,16 @@ import (
|
||||
)
|
||||
|
||||
type List struct {
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"id"`
|
||||
Sort int `json:"sort"`
|
||||
Published int `json:"published"`
|
||||
ShowCompl int `json:"showCompl"`
|
||||
ShowNotes int `json:"showNotes"`
|
||||
Hidden int `json:"hidden"`
|
||||
Max int `json:"max"`
|
||||
Index int `json:"index"`
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"id"`
|
||||
Sort int `json:"sort"`
|
||||
Published int `json:"published"`
|
||||
ShowCompl int `json:"showCompl"`
|
||||
ShowLooping int `json:"showLooping"`
|
||||
ShowNotes int `json:"showNotes"`
|
||||
Hidden int `json:"hidden"`
|
||||
Max int `json:"max"`
|
||||
Index int `json:"index"`
|
||||
}
|
||||
|
||||
func New(r *http.Request) (*List, error) {
|
||||
@@ -41,6 +42,10 @@ func (l *List) SetShowCompl(state bool) {
|
||||
set(state, &l.ShowCompl)
|
||||
}
|
||||
|
||||
func (l *List) SetShowLooping(state bool) {
|
||||
set(state, &l.ShowLooping)
|
||||
}
|
||||
|
||||
func (l *List) SetShowNotes(state bool) {
|
||||
set(state, &l.ShowNotes)
|
||||
}
|
||||
|
||||
@@ -62,11 +62,11 @@ func (a *Ajax) storageListTasks(listID string, filters ...func(t *task.Task) boo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
includeComplete := true
|
||||
includeComplete := false
|
||||
for _, f := range filters {
|
||||
t := &task.Task{Complete: true}
|
||||
if !f(t) {
|
||||
includeComplete = false
|
||||
if f(t) {
|
||||
includeComplete = true
|
||||
}
|
||||
}
|
||||
if includeComplete {
|
||||
@@ -77,11 +77,16 @@ func (a *Ajax) storageListTasks(listID string, filters ...func(t *task.Task) boo
|
||||
results = append(results, completeResults...)
|
||||
}
|
||||
tasks := []*task.Task{}
|
||||
uuids := map[string]struct{}{}
|
||||
for _, result := range results {
|
||||
taskID := path.Base(result)
|
||||
if taskID == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := uuids[taskID]; ok {
|
||||
continue
|
||||
}
|
||||
uuids[taskID] = struct{}{}
|
||||
task, err := a.storageGetTask(taskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -20,15 +20,24 @@ type taskWithDelta struct {
|
||||
func (a *Ajax) loadTasks(w http.ResponseWriter, r *http.Request) error {
|
||||
listID, _, _ := a.Cur(r)
|
||||
filterComplete := filterComplete(form.Get(r, "compl"))
|
||||
filterLooping := filterLooping(form.Get(r, "looping"))
|
||||
filterTags := filterTags(form.ToStrArr(form.Get(r, "t")))
|
||||
filterSubstr := filterSubstr(form.Get(r, "s"))
|
||||
tasks, err := a.storageListTasks(listID, filterComplete, filterTags, filterSubstr)
|
||||
tasks, err := a.storageListTasks(listID, filterComplete, filterTags, filterSubstr, filterLooping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewEncoder(w).Encode(map[string]interface{}{"list": tasks})
|
||||
}
|
||||
|
||||
func filterLooping(looping string) func(t *task.Task) bool {
|
||||
return func(t *task.Task) bool {
|
||||
hasLoop := t.Loop > 0 || t.Cron != ""
|
||||
onlyLooping := looping == "1"
|
||||
return !onlyLooping || hasLoop
|
||||
}
|
||||
}
|
||||
|
||||
func filterComplete(compl string) func(t *task.Task) bool {
|
||||
return func(t *task.Task) bool {
|
||||
return compl == "" || !t.Complete || (compl == "1" && t.Complete)
|
||||
@@ -160,16 +169,27 @@ func (a *Ajax) setPrio(w http.ResponseWriter, r *http.Request) error {
|
||||
func (a *Ajax) moveTask(w http.ResponseWriter, r *http.Request) error {
|
||||
_, taskID, _ := a.Cur(r)
|
||||
toList := form.Get(r, "to")
|
||||
|
||||
list, err := a.storageGetList(toList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
movedTask, err := a.storageGetTask(taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := a.storageDelTask(taskID); err != nil {
|
||||
return err
|
||||
}
|
||||
movedTask.Index = list.NextIndex()
|
||||
if err := a.storageSetTask(toList, movedTask); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.storageSetList(list); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(map[string]interface{}{"total": 1, "list": []*task.Task{movedTask}})
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ type Task struct {
|
||||
Complete bool
|
||||
Note []string
|
||||
Due time.Time
|
||||
Loop time.Duration
|
||||
Cron form.Cron
|
||||
|
||||
Index int
|
||||
}
|
||||
@@ -42,8 +44,9 @@ func New(r *http.Request) (*Task, error) {
|
||||
Tags: append(StrList(form.ToStrArr(form.Get(r, "tag"))), StrList(form.ToStrArr(form.Get(r, "tags")))...),
|
||||
Created: time.Now(),
|
||||
Edited: time.Now(),
|
||||
|
||||
Due: form.ToTime(form.Get(r, "duedate")),
|
||||
Due: form.ToTime(form.Get(r, "duedate")),
|
||||
Loop: form.ToDuration(form.Get(r, "loop")),
|
||||
Cron: form.Cron(form.Get(r, "cron")),
|
||||
}
|
||||
task.SetNote(form.Get(r, "note"))
|
||||
return task, task.validate()
|
||||
@@ -73,7 +76,9 @@ func (t *Task) MarshalJSON() ([]byte, error) {
|
||||
// "dueClass":"",
|
||||
// "dueStr":"",
|
||||
// "dueInt":33330000,
|
||||
// "dueTitle":"Due "}
|
||||
// "dueTitle":"Due ",
|
||||
// "loop": "1m",
|
||||
// "cron": "* * * * *"}
|
||||
// ]}
|
||||
fullFormat := "02 Jan 2006 03:04 PM"
|
||||
shortFormat := "02 Jan"
|
||||
@@ -109,6 +114,8 @@ func (t *Task) MarshalJSON() ([]byte, error) {
|
||||
"dueStr": t.Due.Format(shortFormat),
|
||||
"dueInt": t.Due.Unix(),
|
||||
"dueTitle": "Due ",
|
||||
"loop": t.Loop.String(),
|
||||
"cron": t.Cron,
|
||||
}
|
||||
if t.Due.IsZero() {
|
||||
for k := range m {
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"local/gziphttp"
|
||||
"local/oauth2/oauth2client"
|
||||
"local/router"
|
||||
"local/simpleserve/simpleserve"
|
||||
"local/todo-server/config"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -12,6 +14,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Server) Routes() error {
|
||||
@@ -21,24 +24,31 @@ func (s *Server) Routes() error {
|
||||
}{
|
||||
{
|
||||
path: "/",
|
||||
handler: s.gzip(s.index),
|
||||
handler: s.index,
|
||||
},
|
||||
{
|
||||
path: "/mytinytodo_lang.php",
|
||||
handler: s.gzip(s.lang),
|
||||
handler: s.lang,
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("/themes/%s%s", router.Wildcard, router.Wildcard),
|
||||
handler: s.handleDeviceCSS,
|
||||
},
|
||||
{
|
||||
path: fmt.Sprintf("%s%s", router.Wildcard, router.Wildcard),
|
||||
handler: s.gzip(s.phpProxy),
|
||||
handler: s.phpProxy,
|
||||
},
|
||||
{
|
||||
path: "/ajax.php",
|
||||
handler: s.gzip(s.HandleAjax),
|
||||
handler: s.HandleAjax,
|
||||
},
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if err := s.Add(route.path, route.handler); err != nil {
|
||||
handler := route.handler
|
||||
handler = s.gzip(handler)
|
||||
handler = s.oauth(handler)
|
||||
if err := s.Add(route.path, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -74,6 +84,10 @@ func (s *Server) lang(w http.ResponseWriter, r *http.Request) {
|
||||
`)
|
||||
}
|
||||
|
||||
func (s *Server) toindex(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (s *Server) index(w http.ResponseWriter, r *http.Request) {
|
||||
f, err := os.Open(path.Join(config.Root, "index.html"))
|
||||
if err != nil {
|
||||
@@ -91,19 +105,61 @@ func (s *Server) phpProxy(w http.ResponseWriter, r *http.Request) {
|
||||
s.static(w, r)
|
||||
return
|
||||
}
|
||||
url, err := url.Parse(config.MyTinyTodo)
|
||||
url, err := url.Parse("http://127.0.0.1:64123")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
log.Println("WOULD proxy", url.String(), r.URL.Path)
|
||||
s.index(w, r)
|
||||
s.toindex(w, r)
|
||||
//proxy := httputil.NewSingleHostReverseProxy(url)
|
||||
//proxy.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) static(w http.ResponseWriter, r *http.Request) {
|
||||
s.fileServer.ServeHTTP(w, r)
|
||||
if err := s._static(w, r); err != nil {
|
||||
s.toindex(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) _static(w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method != http.MethodGet {
|
||||
http.FileServer(s.fileDir).ServeHTTP(w, r)
|
||||
return nil
|
||||
}
|
||||
f, err := s.fileDir.Open(path.Clean(r.URL.Path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
r.URL.Path = path.Join(r.URL.Path, "index.html")
|
||||
f, err = s.fileDir.Open(r.URL.Path)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
simpleserve.SetContentTypeIfMedia(w, r)
|
||||
_, err = io.Copy(w, f)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) oauth(h http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if config.OAuth != "" {
|
||||
err := oauth2client.Authenticate(config.OAuth, r.Host, w, r)
|
||||
if err != nil {
|
||||
log.Println("oauth failure", r.Host, ":", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
h(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) gzip(h http.HandlerFunc) http.HandlerFunc {
|
||||
@@ -119,3 +175,14 @@ func (s *Server) gzip(h http.HandlerFunc) http.HandlerFunc {
|
||||
h(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleDeviceCSS(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := r.URL.Query()["pda"]; ok || strings.Contains(r.Header.Get("User-Agent"), "Android") || strings.Contains(r.Header.Get("User-Agent"), "Mobile") {
|
||||
if path.Base(r.URL.Path) == "print.css" {
|
||||
r.URL.Path = path.Join(path.Dir(r.URL.Path), "pda.css")
|
||||
http.Redirect(w, r, r.URL.String(), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
}
|
||||
s.static(w, r)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
type Server struct {
|
||||
*ajax.Ajax
|
||||
*router.Router
|
||||
fileServer http.Handler
|
||||
fileDir http.Dir
|
||||
}
|
||||
|
||||
func New() *Server {
|
||||
@@ -18,10 +18,10 @@ func New() *Server {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fileServer := http.FileServer(http.Dir(config.Root))
|
||||
fileDir := http.Dir(config.Root)
|
||||
return &Server{
|
||||
Ajax: ajax,
|
||||
Router: router.New(),
|
||||
fileServer: fileServer,
|
||||
Ajax: ajax,
|
||||
Router: router.New(),
|
||||
fileDir: fileDir,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user