24 Commits

Author SHA1 Message Date
Bel LaPointe
01adec7db5 set looped task to latest idx 2021-08-30 09:37:40 -06:00
Bel LaPointe
95b10fd9f6 support only looping 2021-08-17 11:47:10 -06:00
bel
fc088ec240 haflways 2021-08-05 23:45:11 -06:00
bel
2763b68bc4 either of cron or loop can be set and unset, mark invalids as invalid and unfinish 2021-07-17 23:58:51 -06:00
bel
f84614a8da add cron to loop 2021-07-17 23:35:24 -06:00
bel
c2ed541604 add cron to ui 2021-07-17 23:17:09 -06:00
bel
8c7fe2e9ef async triggers for either interval or next due, accept cron 2021-07-17 23:14:54 -06:00
bel
9301ddd467 support combo new and old 2021-07-17 22:51:54 -06:00
bel
d5ec073f75 support 1w, 1d for loop 2021-07-17 22:49:10 -06:00
Bel LaPointe
b8f0efc01c every -loop duration, look for completed tasks with loop set and incomplete them 2021-07-17 11:04:52 -06:00
Bel LaPointe
81c8743de7 accept loop param on task 2021-07-17 10:34:22 -06:00
Bel LaPointe
6abfab229a log 2021-04-20 07:55:49 -05:00
Bel LaPointe
ec780f7d9b actually do handler 2021-04-20 07:54:33 -05:00
Bel LaPointe
94a14f8b9d set content type 2021-04-20 07:40:13 -05:00
Bel LaPointe
90dbfd6f5a gr 2021-04-20 07:32:46 -05:00
Bel LaPointe
de5f17e2c9 impl oauth 2021-04-20 07:04:05 -05:00
Bel LaPointe
0e22586e12 fix method 2021-04-20 06:27:57 -05:00
Bel LaPointe
80becbb7a7 weird paths just redir to root 2021-04-20 06:09:02 -05:00
bel
2e98bdff2d Oh hey html has a solution for my css bullshit 2020-03-17 03:28:39 +00:00
bel
8f966c98a4 whoops on css 2020-03-12 04:41:31 +00:00
bel
a0f336ca67 CSS but not lighter for mobile 2020-03-12 04:36:36 +00:00
bel
f8b5eb71e0 remove unused param 2020-03-12 00:57:58 +00:00
bel
7c70ba27cb fix multi-line task note preview 2020-02-02 06:13:27 +00:00
bel
683b7a5f2d Fix pda technically 2020-02-02 05:49:17 +00:00
17 changed files with 386 additions and 52 deletions

View File

@@ -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()
}

View File

@@ -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)

View File

@@ -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>

View File

@@ -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;

View File

@@ -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');
},

View File

@@ -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;
}

View File

@@ -794,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 {
@@ -1367,3 +1368,8 @@ li.mtt-item-hidden {
min-width: 350px;
}
body { filter: invert(80%); background-color: #222; }
#newtask_adv ,
#tagcloudbtn ,
#settings {
display: none;
}

View File

@@ -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
View 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)
}

View File

@@ -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() {

View File

@@ -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)
}
})
}
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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,
}
}