Unittesting begins
|
|
@ -0,0 +1,10 @@
|
||||||
|
gollum
|
||||||
|
mytinytodo
|
||||||
|
**.sw*
|
||||||
|
**/**.sw*
|
||||||
|
*.sw*
|
||||||
|
**/*.sw*
|
||||||
|
todo-server
|
||||||
|
exec-todo-server
|
||||||
|
notes-server
|
||||||
|
exec-notes-server
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"local/args"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Port string
|
||||||
|
StoreType string
|
||||||
|
StoreAddr string
|
||||||
|
StoreUser string
|
||||||
|
StorePass string
|
||||||
|
Public string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Refresh() {
|
||||||
|
if strings.Contains(fmt.Sprint(os.Args), "-test") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
as := args.NewArgSet()
|
||||||
|
as.Append(args.STRING, "port", "port to listen on", "39909")
|
||||||
|
as.Append(args.STRING, "store", "type of store", "map")
|
||||||
|
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, "public", "url of php server", "http://localhost:38808")
|
||||||
|
if err := as.Parse(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Port = ":" + strings.TrimPrefix(as.Get("port").GetString(), ":")
|
||||||
|
StoreType = as.Get("store").GetString()
|
||||||
|
StoreAddr = as.Get("storeaddr").GetString()
|
||||||
|
StoreUser = as.Get("storeuser").GetString()
|
||||||
|
StorePass = as.Get("storepass").GetString()
|
||||||
|
Public = as.Get("public").GetString()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package feed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"local/storage"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Feed struct{}
|
||||||
|
|
||||||
|
func New(db storage.DB, values url.Values) (*Feed, error) {
|
||||||
|
return nil, errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Feed) Read(p []byte) (int, error) {
|
||||||
|
return 0, errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/todo-server/config"
|
||||||
|
"local/todo-server/server"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
s := server.New()
|
||||||
|
if err := s.Routes(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := http.ListenAndServe(config.Port, s); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
Image files page_white_text.png and calendar.png and some icons in buttons.png
|
||||||
|
are (or based on) icons from Silk Icons set by Mark James (http://www.famfamfam.com/lab/icons/silk/),
|
||||||
|
licensed under the Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/).
|
||||||
|
|
||||||
|
Some icons in buttons.png are based on "Silk Companion 1" icons set by Damien Guard
|
||||||
|
(http://damieng.com/creative/icons/silk-companion-1-icons), licensed under the
|
||||||
|
Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/).
|
||||||
|
|
||||||
|
Icons in mzl.png are (or based on) icons from Mozilla Source Code,
|
||||||
|
(http://www.mozilla.org/MPL/#source-code), licensed under the terms
|
||||||
|
of tri-license (MPL 1.1/GPL 2.0/LGPL 2.1).
|
||||||
|
|
||||||
|
Other images in this directory were made by Max Pozdeev and licensed under the terms of GNU GPL v2+.
|
||||||
|
After Width: | Height: | Size: 110 B |
|
After Width: | Height: | Size: 52 B |
|
After Width: | Height: | Size: 67 B |
|
After Width: | Height: | Size: 58 B |
|
After Width: | Height: | Size: 59 B |
|
After Width: | Height: | Size: 330 B |
|
|
@ -0,0 +1 @@
|
||||||
|
Direct access disallowed!
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 270 B |
|
|
@ -0,0 +1,358 @@
|
||||||
|
<?php header("Content-type: text/html; charset=utf-8"); ?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<title><?php mttinfo('title'); ?></title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="<?php mttinfo('template_url'); ?>style.css?v=1.4.3" media="all"/>
|
||||||
|
<?php if (Config::get('rtl')): ?>
|
||||||
|
<link rel="stylesheet" type="text/css" href="<?php mttinfo('template_url'); ?>style_rtl.css?v=1.4.3" media="all"/>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_GET['pda'])): ?>
|
||||||
|
<meta name="viewport" id="viewport" content="width=device-width"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="<?php mttinfo('template_url'); ?>pda.css?v=1.4.3" media="all"/>
|
||||||
|
<?php else: ?>
|
||||||
|
<link rel="stylesheet" type="text/css" href="<?php mttinfo('template_url'); ?>print.css?v=1.4.3" media="print"/>
|
||||||
|
<?php endif; ?>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="<?php mttinfo('mtt_url'); ?>jquery/jquery-1.4.4.min.js"></script>
|
||||||
|
<script type="text/javascript" src="<?php mttinfo('mtt_url'); ?>jquery/jquery-ui-1.8.7.custom.min.js"></script>
|
||||||
|
<script type="text/javascript" src="<?php mttinfo('mtt_url'); ?>jquery/jquery.autocomplete-1.1.js"></script>
|
||||||
|
<script type="text/javascript" src="<?php mttinfo('mtt_url'); ?>mytinytodo.js?v=1.4.3"></script>
|
||||||
|
<script type="text/javascript" src="<?php mttinfo('mtt_url'); ?>mytinytodo_lang.php?v=1.4.3"></script>
|
||||||
|
<script type="text/javascript" src="<?php mttinfo('mtt_url'); ?>mytinytodo_ajax_storage.js?v=1.4.3"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$().ready(function () {
|
||||||
|
|
||||||
|
if (isMobileDevice() && getGetParamValue(window.location.href, 'pda') == null) {
|
||||||
|
window.location.href += '/?pda';
|
||||||
|
}
|
||||||
|
|
||||||
|
<?php if(isset($_GET['pda'])): ?>
|
||||||
|
|
||||||
|
$('body').width(screen.width);
|
||||||
|
$(window).resize(function () {
|
||||||
|
$('body').width(screen.width);
|
||||||
|
});
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
mytinytodo.mttUrl = "<?php mttinfo('mtt_url'); ?>";
|
||||||
|
mytinytodo.templateUrl = "<?php mttinfo('template_url'); ?>";
|
||||||
|
mytinytodo.db = new mytinytodoStorageAjax(mytinytodo);
|
||||||
|
mytinytodo.init({
|
||||||
|
needAuth: <?php echo $needAuth ? "true" : "false"; ?>,
|
||||||
|
isLogged: <?php echo ($needAuth && is_logged()) ? "true" : "false"; ?>,
|
||||||
|
showdate: <?php echo (Config::get('showdate') && !isset($_GET['pda'])) ? "true" : "false"; ?>,
|
||||||
|
singletab: <?php echo (isset($_GET['singletab']) || isset($_GET['pda'])) ? "true" : "false"; ?>,
|
||||||
|
duedatepickerformat: "<?php echo htmlspecialchars(Config::get('dateformat2')); ?>",
|
||||||
|
firstdayofweek: <?php echo (int)Config::get('firstdayofweek'); ?>,
|
||||||
|
autotag: <?php echo Config::get('autotag') ? "true" : "false"; ?>
|
||||||
|
<?php if (isset($_GET['list'])) echo ",openList: " . (int)$_GET['list']; ?>
|
||||||
|
<?php if (isset($_GET['pda'])) echo ", touchDevice: true"; ?>
|
||||||
|
}).loadLists(1);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="wrapper">
|
||||||
|
<div id="container">
|
||||||
|
<div id="mtt_body">
|
||||||
|
|
||||||
|
<h2><?php mttinfo('title'); ?></h2>
|
||||||
|
|
||||||
|
<div id="loading"></div>
|
||||||
|
|
||||||
|
<div id="bar">
|
||||||
|
<div id="msg"><span class="msg-text"></span>
|
||||||
|
<div class="msg-details"></div>
|
||||||
|
</div>
|
||||||
|
<div class="bar-menu">
|
||||||
|
<span class="menu-owner" style="display:none">
|
||||||
|
<a href="#settings" id="settings"><?php _e('a_settings'); ?></a>
|
||||||
|
</span>
|
||||||
|
<span class="bar-delim" style="display:none"> | </span>
|
||||||
|
<span id="bar_auth">
|
||||||
|
<span id="bar_public" style="display:none"><?php _e('public_tasks'); ?> |</span>
|
||||||
|
<a href="#login" id="bar_login" class="nodecor"><u><?php _e('a_login'); ?></u> <span class="arrdown"></span></a>
|
||||||
|
<a href="#logout" id="bar_logout"><?php _e('a_logout'); ?></a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br clear="all"/>
|
||||||
|
|
||||||
|
<div id="page_tasks" style="display:none">
|
||||||
|
|
||||||
|
<div id="lists">
|
||||||
|
<ul class="mtt-tabs"></ul>
|
||||||
|
<div class="mtt-tabs-add-button" title="<?php _e('list_new'); ?>"><span></span>
|
||||||
|
</div>
|
||||||
|
<div id="tabs_buttons">
|
||||||
|
<div class="mtt-tabs-select-button mtt-tabs-button" title="<?php _e('list_select'); ?>">
|
||||||
|
<span></span></div>
|
||||||
|
</div>
|
||||||
|
<div id="list_all" class="mtt-tab mtt-tabs-alltasks mtt-tabs-hidden">
|
||||||
|
<a href="#alltasks"><span><?php _e('alltasks'); ?></span>
|
||||||
|
<div class="list-action"></div>
|
||||||
|
</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="toolbar" class="mtt-htabs">
|
||||||
|
|
||||||
|
<div id="htab_search">
|
||||||
|
<table class="mtt-searchbox">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="mtt-searchbox-c">
|
||||||
|
<input type="text" name="search" value="" maxlength="250" id="search" autocomplete="off"/>
|
||||||
|
<div class="mtt-searchbox-icon mtt-icon-search"></div>
|
||||||
|
<div id="search_close" class="mtt-searchbox-icon mtt-icon-cancelsearch"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="htab_newtask">
|
||||||
|
<table class="mtt-taskbox">
|
||||||
|
<tr>
|
||||||
|
<td class="mtt-tb-cell">
|
||||||
|
<div class="mtt-tb-c">
|
||||||
|
<form id="newtask_form" method="post">
|
||||||
|
<label id="task_placeholder" class="placeholding" for="task">
|
||||||
|
<input type="text" name="task" value="" maxlength="250" id="task" autocomplete="off"/>
|
||||||
|
<span><?php _e('htab_newtask'); ?></span>
|
||||||
|
</label>
|
||||||
|
<div id="newtask_submit" class="mtt-taskbox-icon mtt-icon-submittask" title="<?php _e('btn_add'); ?>"></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" id="newtask_adv" class="mtt-img-button" title="<?php _e('advanced_add'); ?>"><span></span></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="searchbar" style="display:none"><?php _e('searching'); ?>
|
||||||
|
<span id="searchbarkeyword"></span></div>
|
||||||
|
|
||||||
|
<div style="clear:both"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
<span id="taskview" class="mtt-menu-button"><span class="btnstr"><?php _e('tasks'); ?></span> (<span id="total">0</span>) <span class="arrdown"></span></span>
|
||||||
|
<span class="mtt-notes-showhide"><?php _e('notes'); ?>
|
||||||
|
<a href="#" id="mtt-notes-show"><?php _e('notes_show'); ?></a> / <a href="#" id="mtt-notes-hide"><?php _e('notes_hide'); ?></a></span>
|
||||||
|
<span id="mtt_filters"></span>
|
||||||
|
<span id="tagcloudbtn" class="mtt-menu-button"><?php _e('tagcloud'); ?>
|
||||||
|
<span class="arrdown2"></span></span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div id="taskcontainer">
|
||||||
|
<ol id="tasklist" class="sortable"></ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div> <!-- end of page_tasks -->
|
||||||
|
|
||||||
|
|
||||||
|
<div id="page_taskedit" style="display:none">
|
||||||
|
|
||||||
|
<div><a href="#" class="mtt-back-button"><?php _e('go_back'); ?></a></div>
|
||||||
|
|
||||||
|
<h3 class="mtt-inadd"><?php _e('add_task'); ?></h3>
|
||||||
|
<h3 class="mtt-inedit"><?php _e('edit_task'); ?>
|
||||||
|
<div id="taskedit-date" class="mtt-inedit">
|
||||||
|
(<span class="date-created" title="<?php _e('taskdate_created'); ?>"><span></span></span><span class="date-completed" title="<?php _e('taskdate_completed'); ?>"> — <span></span></span>)
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<form id="taskedit_form" name="edittask" method="post">
|
||||||
|
<input type="hidden" name="isadd" value="0"/>
|
||||||
|
<input type="hidden" name="id" value=""/>
|
||||||
|
<div class="form-row form-row-short">
|
||||||
|
<span class="h"><?php _e('priority'); ?></span>
|
||||||
|
<select name="prio">
|
||||||
|
<option value="2">+2</option>
|
||||||
|
<option value="1">+1</option>
|
||||||
|
<option value="0" selected="selected">±0</option>
|
||||||
|
<option value="-1">−1</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-row form-row-short">
|
||||||
|
<span class="h"><?php _e('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-short-end"></div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="h"><?php _e('task'); ?></div>
|
||||||
|
<input type="text" name="task" value="" class="in500" maxlength="250"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="h"><?php _e('note'); ?></div>
|
||||||
|
<textarea name="note" class="in500"></textarea></div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="h"><?php _e('tags'); ?></div>
|
||||||
|
<table cellspacing="0" cellpadding="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="text" name="tags" id="edittags" value="" class="in500" maxlength="250"/>
|
||||||
|
</td>
|
||||||
|
<td class="alltags-cell">
|
||||||
|
<a href="#" id="alltags_show"><?php _e('alltags_show'); ?></a>
|
||||||
|
<a href="#" id="alltags_hide" style="display:none"><?php _e('alltags_hide'); ?></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="form-row" id="alltags" style="display:none;"><?php _e('alltags'); ?>
|
||||||
|
<span class="tags-list"></span></div>
|
||||||
|
<div class="form-row form-bottom-buttons">
|
||||||
|
<input type="submit" value="<?php _e('save'); ?>"/>
|
||||||
|
<input type="button" id="mtt_edit_cancel" class="mtt-back-button" value="<?php _e('cancel'); ?>"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div> <!-- end of page_taskedit -->
|
||||||
|
|
||||||
|
|
||||||
|
<div id="authform" style="display:none">
|
||||||
|
<form id="login_form">
|
||||||
|
<div class="h"><?php _e('password'); ?></div>
|
||||||
|
<div><input type="password" name="password" id="password"/></div>
|
||||||
|
<div><input type="submit" value="<?php _e('btn_login'); ?>"/></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="priopopup" style="display:none">
|
||||||
|
<span class="prio-neg prio-neg-1">−1</span>
|
||||||
|
<span class="prio-zero">±0</span>
|
||||||
|
<span class="prio-pos prio-pos-1">+1</span>
|
||||||
|
<span class="prio-pos prio-pos-2">+2</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="taskviewcontainer" class="mtt-menu-container" style="display:none">
|
||||||
|
<ul>
|
||||||
|
<li id="view_tasks"><?php _e('tasks'); ?> (<span id="cnt_total">0</span>)</li>
|
||||||
|
<li id="view_past"><?php _e('f_past'); ?> (<span id="cnt_past">0</span>)</li>
|
||||||
|
<li id="view_today"><?php _e('f_today'); ?> (<span id="cnt_today">0</span>)</li>
|
||||||
|
<li id="view_soon"><?php _e('f_soon'); ?> (<span id="cnt_soon">0</span>)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tagcloud" style="display:none">
|
||||||
|
<a id="tagcloudcancel" class="mtt-img-button"><span></span></a>
|
||||||
|
<div id="tagcloudload"></div>
|
||||||
|
<div id="tagcloudcontent"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="listmenucontainer" class="mtt-menu-container" style="display:none">
|
||||||
|
<ul>
|
||||||
|
<li class="mtt-need-list mtt-need-real-list" id="btnRenameList"><?php _e('list_rename'); ?></li>
|
||||||
|
<li class="mtt-need-list mtt-need-real-list" id="btnDeleteList"><?php _e('list_delete'); ?></li>
|
||||||
|
<li class="mtt-need-list mtt-need-real-list" id="btnClearCompleted"><?php _e('list_clearcompleted'); ?></li>
|
||||||
|
<li class="mtt-need-list mtt-need-real-list mtt-menu-indicator" submenu="listexportmenucontainer">
|
||||||
|
<div class="submenu-icon"></div><?php _e('list_export'); ?></li>
|
||||||
|
<li class="mtt-menu-delimiter mtt-need-real-list"></li>
|
||||||
|
<li class="mtt-need-list mtt-need-real-list" id="btnPublish">
|
||||||
|
<div class="menu-icon"></div><?php _e('list_publish'); ?></li>
|
||||||
|
<li class="mtt-need-list mtt-need-real-list" id="btnRssFeed">
|
||||||
|
<div class="menu-icon"></div><?php _e('list_rssfeed'); ?></li>
|
||||||
|
<li class="mtt-menu-delimiter mtt-need-real-list"></li>
|
||||||
|
<li class="mtt-need-list mtt-need-real-list sort-item" id="sortByHand">
|
||||||
|
<div class="menu-icon"></div><?php _e('sortByHand'); ?>
|
||||||
|
<span class="mtt-sort-direction"></span></li>
|
||||||
|
<li class="mtt-need-list sort-item" id="sortByDateCreated">
|
||||||
|
<div class="menu-icon"></div><?php _e('sortByDateCreated'); ?>
|
||||||
|
<span class="mtt-sort-direction"></span></li>
|
||||||
|
<li class="mtt-need-list sort-item" id="sortByPrio">
|
||||||
|
<div class="menu-icon"></div><?php _e('sortByPriority'); ?>
|
||||||
|
<span class="mtt-sort-direction"></span></li>
|
||||||
|
<li class="mtt-need-list sort-item" id="sortByDueDate">
|
||||||
|
<div class="menu-icon"></div><?php _e('sortByDueDate'); ?>
|
||||||
|
<span class="mtt-sort-direction"></span></li>
|
||||||
|
<li class="mtt-need-list sort-item" id="sortByDateModified">
|
||||||
|
<div class="menu-icon"></div><?php _e('sortByDateModified'); ?>
|
||||||
|
<span class="mtt-sort-direction"></span></li>
|
||||||
|
<li class="mtt-menu-delimiter"></li>
|
||||||
|
<li class="mtt-need-list" id="btnShowCompleted">
|
||||||
|
<div class="menu-icon"></div><?php _e('list_showcompleted'); ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="listexportmenucontainer" class="mtt-menu-container" style="display:none">
|
||||||
|
<ul>
|
||||||
|
<li class="mtt-need-list mtt-need-real-list" id="btnExportCSV"><?php _e('list_export_csv'); ?></li>
|
||||||
|
<li class="mtt-need-list mtt-need-real-list" id="btnExportICAL"><?php _e('list_export_ical'); ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="taskcontextcontainer" class="mtt-menu-container" style="display:none">
|
||||||
|
<ul>
|
||||||
|
<li id="cmenu_edit"><b><?php _e('action_edit'); ?></b></li>
|
||||||
|
<li id="cmenu_note"><?php _e('action_note'); ?></li>
|
||||||
|
<li id="cmenu_prio" class="mtt-menu-indicator" submenu="cmenupriocontainer">
|
||||||
|
<div class="submenu-icon"></div><?php _e('action_priority'); ?></li>
|
||||||
|
<li id="cmenu_move" class="mtt-menu-indicator" submenu="cmenulistscontainer">
|
||||||
|
<div class="submenu-icon"></div><?php _e('action_move'); ?></li>
|
||||||
|
<li id="cmenu_delete"><?php _e('action_delete'); ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="cmenupriocontainer" class="mtt-menu-container" style="display:none">
|
||||||
|
<ul>
|
||||||
|
<li id="cmenu_prio:2">
|
||||||
|
<div class="menu-icon"></div>
|
||||||
|
+2
|
||||||
|
</li>
|
||||||
|
<li id="cmenu_prio:1">
|
||||||
|
<div class="menu-icon"></div>
|
||||||
|
+1
|
||||||
|
</li>
|
||||||
|
<li id="cmenu_prio:0">
|
||||||
|
<div class="menu-icon"></div>±0
|
||||||
|
</li>
|
||||||
|
<li id="cmenu_prio:-1">
|
||||||
|
<div class="menu-icon"></div>−1
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="cmenulistscontainer" class="mtt-menu-container" style="display:none">
|
||||||
|
<ul>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="slmenucontainer" class="mtt-menu-container" style="display:none">
|
||||||
|
<ul>
|
||||||
|
<li id="slmenu_list:-1" class="list-id--1 mtt-need-list" <?php if (is_readonly()) echo 'style="display:none"' ?>>
|
||||||
|
<div class="menu-icon"></div>
|
||||||
|
<a href="#alltasks"><?php _e('alltasks'); ?></a></li>
|
||||||
|
<li class="mtt-menu-delimiter slmenu-lists-begin mtt-need-list" <?php if (is_readonly()) echo 'style="display:none"' ?>></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="page_ajax" style="display:none"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="space"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="footer">
|
||||||
|
<div id="footer_content">Powered by
|
||||||
|
<strong><a href="http://www.mytinytodo.net/">myTinyTodo</a></strong> 1.4.3
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<!-- r390 -->
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#body {
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-right: 1px;
|
||||||
|
padding: 1px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bar_login, #bar_logout {
|
||||||
|
padding-right: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabs ul {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabs ul li {
|
||||||
|
width: 70px;
|
||||||
|
margin-right: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#htab_search {
|
||||||
|
width: 40%;
|
||||||
|
max-width: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-searchbox {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-searchbox td {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbar.mtt-intask #htab_search {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbar.mtt-insearch #htab_newtask {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbar.mtt-insearch #htab_search {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbar.mtt-insearch .mtt-searchbox td {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasklist li {
|
||||||
|
padding: 0.5em 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-date {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-note-actions {
|
||||||
|
display: block;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-note-block {
|
||||||
|
margin-left: 0px;
|
||||||
|
border-left: 1px solid #777777;
|
||||||
|
background: none;
|
||||||
|
padding-left: 4px;
|
||||||
|
margin-top: 1px;
|
||||||
|
padding-top: 0px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-note-area textarea {
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-middle {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasklist li .task-through {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasklist li:hover {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasklist li.task-expanded .task-note-block {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#tasklist li.task-expanded .task-toggle { background-position:-32px 0; }*/
|
||||||
|
#tasklist li.clicked {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasklist li.clicked .task-actions {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasklist li.clicked .task-through {
|
||||||
|
white-space: normal;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasklist li.clicked.task-has-note .task-note-block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#tasklist li.clicked.task-has-note .task-toggle { background-position:-48px 0; } */
|
||||||
|
#tasklist li.clicked.doubleclicked.task-has-note .task-note-block {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-middle {
|
||||||
|
margin-left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page_taskedit {
|
||||||
|
max-width: 99.5%;
|
||||||
|
border: none;
|
||||||
|
position: static;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page_taskedit .form-table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page_taskedit .form-row .in500 {
|
||||||
|
color: #444444;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page_taskedit .form-row textarea {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading {
|
||||||
|
padding: 0px;
|
||||||
|
padding-top: 1px;
|
||||||
|
padding-right: 1px;
|
||||||
|
height: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading img { /*width:8px; height:8px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#tagcloud {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-settings-table .in350 {
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-notes-showhide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
html {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 0;
|
||||||
|
min-height: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
border-bottom: 2px solid #777777;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lists {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-bar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#taskviewcontainer {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#taskviewcontainer img {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasklist {
|
||||||
|
list-style-type: decimal;
|
||||||
|
list-style-position: outside;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasklist li {
|
||||||
|
padding-left: 0px;
|
||||||
|
margin-left: 30px;
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.task-note-block {
|
||||||
|
border-left: 1px solid #777777;
|
||||||
|
background: none;
|
||||||
|
padding-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding-top: 0px;
|
||||||
|
font-size: 9pt;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-middle {
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-left {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-date {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tasklist li.today, #tasklist li.past {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-color: #dedede;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-prio {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.task-completed {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer_content {
|
||||||
|
border-top: 1px solid #777777;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer_content a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tagcloudbtn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-notes-showhide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#taskview img {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
body {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
float: right;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-menu {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lists .mtt-tabs {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-tabs li {
|
||||||
|
float: right;
|
||||||
|
margin: 1px 0 0 3px;
|
||||||
|
border-left: none;
|
||||||
|
border-right: 1px solid #ededed;
|
||||||
|
background: #fbfbfb url(images/tab_hover.gif) no-repeat top left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-tabs li.mtt-tabs-selected {
|
||||||
|
border-left: none;
|
||||||
|
border-right: 1px solid #ededed;
|
||||||
|
background: #ededed url(images/corner_left.gif) no-repeat top left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabs_buttons {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-tabs-button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tagcloudbtn {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#htab_search {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#task {
|
||||||
|
padding: 2px 2px 2px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#task_placeholder span {
|
||||||
|
right: 0px;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-taskbox-icon.mtt-icon-submittask {
|
||||||
|
left: 4px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-searchbox-icon.mtt-icon-search {
|
||||||
|
right: 4px;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mtt-searchbox-icon.mtt-icon-cancelsearch {
|
||||||
|
left: 4px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-actions {
|
||||||
|
float: left;
|
||||||
|
text-align: left;
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-left {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-toggle {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-middle {
|
||||||
|
margin-right: 40px;
|
||||||
|
margin-left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-date {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duedate {
|
||||||
|
float: left;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duedate-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duedate:before {
|
||||||
|
content: '\2190';
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-through-right {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#taskedit-date {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page_taskedit .form-row-short {
|
||||||
|
float: right;
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-prio {
|
||||||
|
float: right;
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alltags-cell {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package ajax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/storage"
|
||||||
|
"local/todo-server/config"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ajax struct {
|
||||||
|
DB storage.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() (*Ajax, error) {
|
||||||
|
db, err := storage.New(storage.TypeFromString(config.StoreType), config.StoreAddr, config.StoreUser, config.StorePass)
|
||||||
|
return &Ajax{
|
||||||
|
DB: db,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) HandleAjax(w http.ResponseWriter, r *http.Request) {
|
||||||
|
params := r.URL.Query()
|
||||||
|
var foo func(http.ResponseWriter, *http.Request) error
|
||||||
|
if v := params.Get("loadLists"); v != "" {
|
||||||
|
foo = a.loadLists
|
||||||
|
} else if v := params.Get("loadTasks"); v != "" {
|
||||||
|
foo = a.loadTasks
|
||||||
|
} else if v := params.Get("newTask"); v != "" {
|
||||||
|
foo = a.newTask
|
||||||
|
} else if v := params.Get("fullNewTask"); v != "" {
|
||||||
|
foo = a.newTask
|
||||||
|
} else if v := params.Get("deleteTask"); v != "" {
|
||||||
|
foo = a.deleteTask
|
||||||
|
} else if v := params.Get("completeTask"); v != "" {
|
||||||
|
foo = a.completeTask
|
||||||
|
} else if v := params.Get("editNote"); v != "" {
|
||||||
|
foo = a.editNote
|
||||||
|
} else if v := params.Get("editTask"); v != "" {
|
||||||
|
foo = a.editTask
|
||||||
|
} else if v := params.Get("changeOrder"); v != "" {
|
||||||
|
foo = a.changeOrder
|
||||||
|
} else if v := params.Get("suggestTags"); v != "" {
|
||||||
|
foo = a.suggestTags
|
||||||
|
} else if v := params.Get("setPrio"); v != "" {
|
||||||
|
foo = a.setPrio
|
||||||
|
} else if v := params.Get("tagCloud"); v != "" {
|
||||||
|
foo = a.tagCloud
|
||||||
|
} else if v := params.Get("addList"); v != "" {
|
||||||
|
foo = a.addList
|
||||||
|
} else if v := params.Get("renameList"); v != "" {
|
||||||
|
foo = a.renameList
|
||||||
|
} else if v := params.Get("deleteList"); v != "" {
|
||||||
|
foo = a.deleteList
|
||||||
|
} else if v := params.Get("setSort"); v != "" {
|
||||||
|
foo = a.setSort
|
||||||
|
} else if v := params.Get("publishList"); v != "" {
|
||||||
|
foo = a.publishList
|
||||||
|
} else if v := params.Get("moveTask"); v != "" {
|
||||||
|
foo = a.moveTask
|
||||||
|
} else if v := params.Get("changeListOrder"); v != "" {
|
||||||
|
foo = a.changeListOrder
|
||||||
|
} else if v := params.Get("parseTaskStr"); v != "" {
|
||||||
|
foo = a.parseTaskStr
|
||||||
|
} else if v := params.Get("clearCompletedInList"); v != "" {
|
||||||
|
foo = a.clearCompletedInList
|
||||||
|
} else if v := params.Get("setShowNotesInList"); v != "" {
|
||||||
|
foo = a.setShowNotesInList
|
||||||
|
} else if v := params.Get("setHideList"); v != "" {
|
||||||
|
foo = a.setHideList
|
||||||
|
}
|
||||||
|
if err := foo(w, r); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package ajax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/todo-server/config"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
mockAjax()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockAjax() *Ajax {
|
||||||
|
config.StoreType = "map"
|
||||||
|
ajax, err := New()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ajax.storageSetCur("list")
|
||||||
|
return ajax
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"html"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type readCloser struct {
|
||||||
|
io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc readCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(r *http.Request, k string) string {
|
||||||
|
s := r.FormValue(k)
|
||||||
|
if s == "" {
|
||||||
|
b, _ := ioutil.ReadAll(r.Body)
|
||||||
|
var m map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(b, &m); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
v, _ := m[k]
|
||||||
|
s = strings.TrimPrefix(strings.TrimSuffix(string(v), `"`), `"`)
|
||||||
|
r.Body = readCloser{bytes.NewReader(b)}
|
||||||
|
}
|
||||||
|
s = html.UnescapeString(s)
|
||||||
|
s = strings.ReplaceAll(s, "\r", "")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToInt(s string) int {
|
||||||
|
v, _ := strconv.Atoi(s)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToStrArr(k string) []string {
|
||||||
|
arr := strings.Split(k, ",")
|
||||||
|
outArr := []string{}
|
||||||
|
for i := range arr {
|
||||||
|
s := strings.TrimSpace(arr[i])
|
||||||
|
if len(s) > 0 {
|
||||||
|
outArr = append(outArr, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outArr
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTime(s string) time.Time {
|
||||||
|
v, _ := time.Parse("2006-01-02 15:04:05", s)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
r := testReq()
|
||||||
|
if v := Get(r, "a"); v != "b" {
|
||||||
|
t.Error(v)
|
||||||
|
}
|
||||||
|
if v := Get(r, "c"); v != "4" {
|
||||||
|
t.Error(v)
|
||||||
|
}
|
||||||
|
if v := Get(r, "d"); v != "e, f,g" {
|
||||||
|
t.Error(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToStrArr(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
in string
|
||||||
|
out int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: "4",
|
||||||
|
out: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "a,b,c",
|
||||||
|
out: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: " a, b, c ",
|
||||||
|
out: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "a,b, c",
|
||||||
|
out: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "a,b",
|
||||||
|
out: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "a,",
|
||||||
|
out: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "a",
|
||||||
|
out: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "",
|
||||||
|
out: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
if len(ToStrArr(c.in)) != c.out {
|
||||||
|
t.Error(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToTime(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
in string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: "2001-02-03 04:05:06",
|
||||||
|
out: "2001-02-03 04:05:06",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "5",
|
||||||
|
out: "0001-01-01 00:00:00",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
time := ToTime(c.in)
|
||||||
|
if v := time.Format("2006-01-02 15:04:05"); v != c.out {
|
||||||
|
t.Error(c, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToInt(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
in string
|
||||||
|
out int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: "4",
|
||||||
|
out: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "a",
|
||||||
|
out: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "",
|
||||||
|
out: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "-1",
|
||||||
|
out: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "5",
|
||||||
|
out: 5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
if ToInt(c.in) != c.out {
|
||||||
|
t.Error(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testReq() *http.Request {
|
||||||
|
return httptest.NewRequest("POST", "/path/to", strings.NewReader(`{
|
||||||
|
"a": "b",
|
||||||
|
"c": 4,
|
||||||
|
"d": "e, f,g"
|
||||||
|
}`))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package ajax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type List struct{}
|
||||||
|
|
||||||
|
func (a *Ajax) loadLists(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) changeOrder(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) addList(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) renameList(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) deleteList(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) setSort(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) publishList(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) changeListOrder(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) clearCompletedInList(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) setShowNotesInList(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) setHideList(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package ajax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type List struct{}
|
||||||
|
|
||||||
|
func New(r *http.Request) (*List, error) {
|
||||||
|
return &List{}, errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) loadLists() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) changeOrder() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) addList() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) renameList() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) deleteList() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) setSort() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) publishList() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) changeListOrder() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) clearCompletedInList() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) setShowNotesInList() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *List) setHideList() error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package ajax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"local/todo-server/server/ajax/form"
|
||||||
|
"local/todo-server/server/ajax/task"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Ajax) Cur(r *http.Request) (string, string, []string) {
|
||||||
|
listID, _ := a.storageGetCur()
|
||||||
|
taskID := form.Get(r, "id")
|
||||||
|
tags, _ := a.storageGetCurTags()
|
||||||
|
return listID, taskID, tags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageListTasks(listID string, filters ...func(t *task.Task) bool) ([]*task.Task, error) {
|
||||||
|
results, err := a.DB.List(nil, listID+"/", listID+"/}")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tasks := []*task.Task{}
|
||||||
|
for _, result := range results {
|
||||||
|
taskID := strings.TrimPrefix(result, listID+"/")
|
||||||
|
task, err := a.storageGetTask(listID, taskID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
filtered := true
|
||||||
|
for _, f := range filters {
|
||||||
|
if !f(task) {
|
||||||
|
filtered = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if filtered {
|
||||||
|
tasks = append(tasks, task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tasks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageGetTask(listID, taskID string) (*task.Task, error) {
|
||||||
|
var task task.Task
|
||||||
|
err := a.storageGet(path.Join(listID, taskID), &task)
|
||||||
|
return &task, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageSetTask(listID, taskID string, task *task.Task) error {
|
||||||
|
return a.storageSet(path.Join(listID, taskID), *task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageDelTask(listID, taskID string) error {
|
||||||
|
return a.storageDel(path.Join(listID, taskID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageGetList(listID string) (*List, error) {
|
||||||
|
var list List
|
||||||
|
err := a.storageGet(listID, &list)
|
||||||
|
return &list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageSetList(listID string, list *List) error {
|
||||||
|
return a.storageSet(listID, *list)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageDelList(listID string) error {
|
||||||
|
return a.storageDel(listID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageSetCurTags(tags []string) error {
|
||||||
|
return a.storageSet("currentTags", tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageGetCurTags() ([]string, error) {
|
||||||
|
var tags []string
|
||||||
|
err := a.storageGet("currentTags", &tags)
|
||||||
|
return tags, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageSetCur(listID string) error {
|
||||||
|
return a.storageSet("currentList", listID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageGetCur() (string, error) {
|
||||||
|
var listID string
|
||||||
|
err := a.storageGet("currentList", &listID)
|
||||||
|
return listID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageSet(key string, value interface{}) error {
|
||||||
|
buff := bytes.NewBuffer(nil)
|
||||||
|
encoder := gob.NewEncoder(buff)
|
||||||
|
if err := encoder.Encode(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return a.DB.Set(key, buff.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageGet(key string, value interface{}) error {
|
||||||
|
b, err := a.DB.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buff := bytes.NewBuffer(b)
|
||||||
|
decoder := gob.NewDecoder(buff)
|
||||||
|
return decoder.Decode(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) storageDel(key string) error {
|
||||||
|
return a.DB.Set(key, nil)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ajax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Ajax) suggestTags(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) tagCloud(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package ajax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"local/todo-server/server/ajax/form"
|
||||||
|
"local/todo-server/server/ajax/task"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Ajax) loadTasks(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
listID, _, _ := a.Cur(r)
|
||||||
|
filterComplete := func(t *task.Task) bool {
|
||||||
|
if form.Get(r, "compl") == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return t.Complete == (form.Get(r, "compl") != "0")
|
||||||
|
}
|
||||||
|
filterTags := func(t *task.Task) bool {
|
||||||
|
if form.Get(r, "t") == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
whitelistTags := form.ToStrArr(form.Get(r, "t"))
|
||||||
|
if len(whitelistTags) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
whitelistTagMap := make(map[string]struct{})
|
||||||
|
for _, tag := range whitelistTags {
|
||||||
|
whitelistTagMap[tag] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, tag := range t.Tags {
|
||||||
|
if _, ok := whitelistTagMap[tag]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
filterSubstr := func(t *task.Task) bool {
|
||||||
|
substr := form.Get(r, "s")
|
||||||
|
return substr == "" || strings.Contains(fmt.Sprintf("%+v", t), substr)
|
||||||
|
}
|
||||||
|
tasks, err := a.storageListTasks(listID, filterComplete, filterTags, filterSubstr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.NewEncoder(w).Encode(map[string]interface{}{"list": tasks})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) newTask(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
listID, task, err := a.makeTask(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return a.storageSetTask(listID, task.UUID, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) makeTask(r *http.Request) (string, *task.Task, error) {
|
||||||
|
listID, _, tags := a.Cur(r)
|
||||||
|
task, err := task.New(r)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
task.AppendTags(tags)
|
||||||
|
return listID, task, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) deleteTask(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
listID, taskID, _ := a.Cur(r)
|
||||||
|
return a.storageDelTask(listID, taskID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) completeTask(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
listID, taskID, _ := a.Cur(r)
|
||||||
|
task, err := a.storageGetTask(listID, taskID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task.SetComplete(form.Get(r, "compl") == "1")
|
||||||
|
return a.storageSetTask(listID, taskID, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) editNote(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
listID, taskID, _ := a.Cur(r)
|
||||||
|
task, err := a.storageGetTask(listID, taskID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task.SetNote(form.Get(r, "note"))
|
||||||
|
return a.storageSetTask(listID, taskID, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) editTask(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
listID, task, err := a.makeTask(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, taskID, _ := a.Cur(r)
|
||||||
|
task.UUID = taskID
|
||||||
|
task.ID = task.UUID
|
||||||
|
return a.storageSetTask(listID, task.UUID, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) setPrio(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
listID, taskID, _ := a.Cur(r)
|
||||||
|
task, err := a.storageGetTask(listID, taskID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task.SetPrio(form.ToInt(form.Get(r, "prio")))
|
||||||
|
return a.storageSetTask(listID, taskID, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) moveTask(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
listID, taskID, _ := a.Cur(r)
|
||||||
|
toList := form.Get(r, "to")
|
||||||
|
task, err := a.storageGetTask(listID, taskID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.storageSetTask(listID, taskID, nil)
|
||||||
|
return a.storageSetTask(toList, taskID, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Ajax) parseTaskStr(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"local/todo-server/server/ajax/form"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Task struct {
|
||||||
|
ID string
|
||||||
|
UUID string
|
||||||
|
Title string
|
||||||
|
Priority int
|
||||||
|
Tags []string
|
||||||
|
Created time.Time
|
||||||
|
Edited time.Time
|
||||||
|
|
||||||
|
Completed time.Time
|
||||||
|
Complete bool
|
||||||
|
Note []string
|
||||||
|
Due time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(r *http.Request) (*Task, error) {
|
||||||
|
task := &Task{
|
||||||
|
UUID: uuid.New().String(),
|
||||||
|
Title: form.Get(r, "title"),
|
||||||
|
Priority: form.ToInt(form.Get(r, "prio")),
|
||||||
|
Tags: form.ToStrArr(form.Get(r, "tags")),
|
||||||
|
Created: time.Now(),
|
||||||
|
Edited: time.Now(),
|
||||||
|
|
||||||
|
Due: form.ToTime(form.Get(r, "duedate")),
|
||||||
|
}
|
||||||
|
task.ID = task.UUID
|
||||||
|
task.SetNote(form.Get(r, "note"))
|
||||||
|
return task, task.validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) AppendTags(tags []string) {
|
||||||
|
t.touch()
|
||||||
|
t.Tags = append(t.Tags, tags...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) SetComplete(state bool) {
|
||||||
|
t.touch()
|
||||||
|
t.Complete = state
|
||||||
|
if t.Complete {
|
||||||
|
t.Completed = time.Now()
|
||||||
|
} else {
|
||||||
|
t.Completed = time.Time{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) SetPrio(prio int) {
|
||||||
|
t.touch()
|
||||||
|
t.Priority = prio
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) SetNote(note string) {
|
||||||
|
t.touch()
|
||||||
|
t.Note = strings.Split(note, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) touch() {
|
||||||
|
t.Edited = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) validate() error {
|
||||||
|
if t.Title == "" {
|
||||||
|
return errors.New("task cannot have nil title")
|
||||||
|
}
|
||||||
|
if err := t.smartSyntax(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Task) smartSyntax() error {
|
||||||
|
re := regexp.MustCompile(`^(/([+-]{0,1}\d+)?/)?(.*?)(\s+/([^/]*)/$)?$|`)
|
||||||
|
matches := re.FindAllStringSubmatch(t.Title, 1)[0]
|
||||||
|
if len(matches) != 6 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if matches[1] != "" {
|
||||||
|
t.Priority = form.ToInt(matches[1])
|
||||||
|
}
|
||||||
|
if matches[3] != "" {
|
||||||
|
t.Title = matches[3]
|
||||||
|
}
|
||||||
|
if matches[5] != "" {
|
||||||
|
t.Tags = form.ToStrArr(matches[5])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
if _, err := New(toReq(map[string]interface{}{})); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if _, err := New(toReq(map[string]interface{}{"title": ""})); err == nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if task, err := New(toReq(map[string]interface{}{"title": "i want dogs /a,b,c/"})); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if task.Title != "i want dogs" {
|
||||||
|
t.Error(task.Title)
|
||||||
|
} else if fmt.Sprint(task.Tags) != "[a b c]" {
|
||||||
|
t.Error(task.Tags)
|
||||||
|
} else {
|
||||||
|
was := task.Edited
|
||||||
|
task.touch()
|
||||||
|
if was == task.Edited {
|
||||||
|
t.Error(was)
|
||||||
|
}
|
||||||
|
was = task.Edited
|
||||||
|
task.SetNote("hell\nno")
|
||||||
|
if was == task.Edited {
|
||||||
|
t.Error(was)
|
||||||
|
} else if len(task.Note) != 2 {
|
||||||
|
t.Error(task.Note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toReq(m map[string]interface{}) *http.Request {
|
||||||
|
if m == nil {
|
||||||
|
m = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
els := map[string]interface{}{
|
||||||
|
"title": "title",
|
||||||
|
"prio": 1,
|
||||||
|
"tags": "a, b,c",
|
||||||
|
"duedate": "2010-02-03 05:06:07",
|
||||||
|
"note": "hello\nworld\ni\nam a note\nand\ni have\nlots\nof\nlines",
|
||||||
|
}
|
||||||
|
for k := range els {
|
||||||
|
if _, ok := m[k]; !ok {
|
||||||
|
m[k] = els[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(m)
|
||||||
|
return httptest.NewRequest("POST", "/paht", bytes.NewReader(b))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package ajax
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"local/todo-server/server/ajax/task"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAjaxLoadTasks(t *testing.T) {
|
||||||
|
a := mockAjax()
|
||||||
|
|
||||||
|
func() {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
a.loadTasks(w, r)
|
||||||
|
var result struct {
|
||||||
|
List []string `json:"list"`
|
||||||
|
}
|
||||||
|
if v := w.Code; v != http.StatusOK {
|
||||||
|
t.Error(v)
|
||||||
|
} else if err := json.NewDecoder(w.Body).Decode(&result); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if len(result.List) != 0 {
|
||||||
|
t.Error(result)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
a.storageSetTask("list", "task", &task.Task{Title: "hi"})
|
||||||
|
|
||||||
|
func() {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r := httptest.NewRequest("GET", "/", nil)
|
||||||
|
a.loadTasks(w, r)
|
||||||
|
var result struct {
|
||||||
|
List []task.Task `json:"list"`
|
||||||
|
}
|
||||||
|
if v := w.Code; v != http.StatusOK {
|
||||||
|
t.Error(v)
|
||||||
|
} else if err := json.NewDecoder(w.Body).Decode(&result); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if len(result.List) != 1 {
|
||||||
|
t.Error(result)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"local/router"
|
||||||
|
"local/todo-server/config"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) Routes() error {
|
||||||
|
routes := []struct {
|
||||||
|
path string
|
||||||
|
handler http.HandlerFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
path: fmt.Sprintf("%s%s", router.Wildcard, router.Wildcard),
|
||||||
|
handler: s.phpProxy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: fmt.Sprintf("/ajax.php"),
|
||||||
|
handler: s.HandleAjax,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
if err := s.Add(route.path, route.handler); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) index(w http.ResponseWriter, r *http.Request) {
|
||||||
|
f, err := os.Open(path.Join(config.Public, "index.php"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
io.Copy(w, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) phpProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
url, err := url.Parse(config.Public)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/router"
|
||||||
|
"local/todo-server/server/ajax"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*ajax.Ajax
|
||||||
|
*router.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Server {
|
||||||
|
ajax, err := ajax.New()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &Server{
|
||||||
|
Ajax: ajax,
|
||||||
|
Router: router.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# lists
|
||||||
|
|
||||||
|
**id int
|
||||||
|
**uuid char36
|
||||||
|
* ow int
|
||||||
|
* name char50
|
||||||
|
* d_created int
|
||||||
|
* d_edited int
|
||||||
|
* sorting tinyint
|
||||||
|
* published tinyint
|
||||||
|
* taskview int
|
||||||
|
|
||||||
|
# todolist
|
||||||
|
|
||||||
|
**id int
|
||||||
|
**uuid char36
|
||||||
|
**list_id int
|
||||||
|
* d_created int
|
||||||
|
* d_completed int
|
||||||
|
* d_edited int
|
||||||
|
* compl tinyint
|
||||||
|
* title char250
|
||||||
|
* note text
|
||||||
|
* prio tinyint
|
||||||
|
* ow int
|
||||||
|
* tags char600
|
||||||
|
* tags_ids char250
|
||||||
|
* duedate time.date
|
||||||
|
|
||||||
|
# tag2task
|
||||||
|
|
||||||
|
* tag_id int
|
||||||
|
* task_id int
|
||||||
|
* list_id int
|
||||||