From efbdc3bbd6cba8691391ec2192ea96adbfb8c029 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Wed, 19 Mar 2014 22:45:31 +0100 Subject: Initial commit --- .htaccess | 17 +++ LICENSE | 4 + README.markdown | 38 +++++ autoload/Todo/Lists.php | 141 ++++++++++++++++++ autoload/Todo/Model/TodoList.php | 100 +++++++++++++ autoload/Todo/Model/User.php | 66 +++++++++ autoload/Todo/SessionHelper.php | 49 +++++++ autoload/Todo/UserManager.php | 203 ++++++++++++++++++++++++++ config.ini | 7 + index.php | 41 ++++++ ui/blank.html | 0 ui/deleteAccount.html | 6 + ui/deleteList.html | 8 + ui/img/sprites.png | Bin 0 -> 924 bytes ui/img/sprites.xcf | Bin 0 -> 13642 bytes ui/list.html | 46 ++++++ ui/login.html | 8 + ui/mails/activationcode | 6 + ui/mails/pwreset | 11 ++ ui/master.html | 63 ++++++++ ui/noListSelected.html | 1 + ui/pwreset.html | 7 + ui/pwresetRequest.html | 6 + ui/register.html | 10 ++ ui/settings.html | 8 + ui/style.css | 306 +++++++++++++++++++++++++++++++++++++++ ui/welcome.html | 3 + 27 files changed, 1155 insertions(+) create mode 100755 .htaccess create mode 100644 LICENSE create mode 100644 README.markdown create mode 100644 autoload/Todo/Lists.php create mode 100644 autoload/Todo/Model/TodoList.php create mode 100644 autoload/Todo/Model/User.php create mode 100644 autoload/Todo/SessionHelper.php create mode 100644 autoload/Todo/UserManager.php create mode 100755 config.ini create mode 100755 index.php create mode 100644 ui/blank.html create mode 100644 ui/deleteAccount.html create mode 100644 ui/deleteList.html create mode 100644 ui/img/sprites.png create mode 100644 ui/img/sprites.xcf create mode 100644 ui/list.html create mode 100644 ui/login.html create mode 100644 ui/mails/activationcode create mode 100644 ui/mails/pwreset create mode 100644 ui/master.html create mode 100644 ui/noListSelected.html create mode 100644 ui/pwreset.html create mode 100644 ui/pwresetRequest.html create mode 100644 ui/register.html create mode 100644 ui/settings.html create mode 100644 ui/style.css create mode 100644 ui/welcome.html diff --git a/.htaccess b/.htaccess new file mode 100755 index 0000000..68d1f73 --- /dev/null +++ b/.htaccess @@ -0,0 +1,17 @@ +# Enable rewrite engine and route requests to framework +RewriteEngine On + +# Some servers require you to specify the `RewriteBase` directive +# In such cases, it should be the path (relative to the document root) +# containing this .htaccess file +# +# RewriteBase / + +RewriteCond %{REQUEST_URI} \.ini$ +RewriteRule \.ini$ - [R=404] + +RewriteCond %{REQUEST_FILENAME} !-l +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule .* index.php [L,QSA] +RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c74f13c --- /dev/null +++ b/LICENSE @@ -0,0 +1,4 @@ + DO WHATEVER THE FUCK YOU WANT, PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHATEVER THE FUCK YOU WANT. diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..14fe1cc --- /dev/null +++ b/README.markdown @@ -0,0 +1,38 @@ +# todo + +A simple todo web application. + +## Install + +First, you need to fetch some dependencies: + +### Fat-Free Framework + +Download the Fat-Free Framework from [http://fatfreeframework.com/](http://fatfreeframework.com/) and extract the content of the `lib` folder into the `lib` folder of this application. + +### DejaVu Sans ExtraLight + +Get this light front from [http://www.fonts2u.com/download/dejavu-sans-extralight.font-face](http://www.fonts2u.com/download/dejavu-sans-extralight.font-face) and extract the files into the `ui/DejaVu_Sans_ExtraLight` directory + +Now that all dependencies are fetched, you can copy the application to your webserver. Just one thing: Change the permissions of the `tmp` directory so the webserver can write to it. + +## Configuration + +Todo is configured with the `config.ini` file. + +* `DEBUG` – Unless you intend to further develop this application, leave this to `0` +* `sql_dsn` – The [PDO DSN]() for connecting to the database +* `sql_user` – Username for the database access +* `sql_pass` – Password for the database access +* `mail_from` – Outgoing mails (confirmation codes, password reset mails) will use this E-Mail address for the `From` field +* `appname` – The name of the application + +### Protecting the configuration file + +It is usually a bad idea that the config file is accessible via the web, since people can then see the username and password of your database. + +One possibility to prevent this, is to move the file into a directory that can not be accessed via the web and then modifying `index.php`: Search for the line `$f3->config('config.ini');` and change the `config.ini` part to point to the new location. + +## Or just use my public installation + +[todo.kch42.net](http://todo.kch42.net/) diff --git a/autoload/Todo/Lists.php b/autoload/Todo/Lists.php new file mode 100644 index 0000000..9f951a3 --- /dev/null +++ b/autoload/Todo/Lists.php @@ -0,0 +1,141 @@ +set('content', ($this->user === NULL) ? 'login.html' : 'noListSelected.html'); + } + + private function populateListData($f3, $l) { + $f3->set('listid', $l->id); + $f3->set('listname', $l->name); + $f3->set('listdata', $l->itemsToArray()); + } + + private function getList($f3, $id) { + $l = new \Todo\Model\TodoList($f3->get('DB')); + if((!$l->byID($id)) || $l->user !== $this->user->id) { + $f3->set('content', 'blank.html'); + $f3->set('error', 'List not found'); + $f3->error(404); # TODO: Meh, this stops F3 from rendering the template.... + return NULL; + } + return $l; + } + + public function deleteList($f3, $args) { + if($this->needLogin($f3)) { + return; + } + + if(!($l = $this->getList($f3, $args['list']))) { + return; + } + + if(($f3->get('VERB') === 'POST') && ($f3->get('POST.confirm') === 'OK')) { + $l->deleteList(); + + $f3->set('success', 'List deleted'); + $f3->set('content', 'blank.html'); + } else { + $this->populateListData($f3, $l); + $f3->set('content', 'deleteList.html'); + } + } + + public function newList($f3) { + if($this->needLogin($f3)) { + return; + } + + if($f3->get('VERB') !== 'POST') { + return; + } + + if($f3->get('POST.listname') === '') { + $f3->set('error', 'List name must not be empty'); + $f3->set('content', 'blank.html'); + return; + } + + $l = new \Todo\Model\TodoList($f3->get('DB')); + $l->user = $this->user->id; + $l->name = $f3->get('POST.listname'); + $l->save(); + + $f3->set('success', 'List created!'); + + $f3->set('content', 'list.html'); + $this->populateListData($f3, $l); + } + + public function showList($f3, $args) { + if($this->needLogin($f3)) { + return; + } + + $f3->set('content', 'list.html'); + + if(!($l = $this->getList($f3, $args['list']))) { + return; + } + + if($f3->get('VERB') === 'POST') { + $ok = true; + switch($f3->get('POST.action')) { + case 'setname': + if($f3->get('POST.name') === '') { + $f3->set('error', 'List name must not be empty'); + $ok = false; + } else { + $l->name = $f3->get('POST.name'); + $l->save(); + } + break; + case 'additem': + if($f3->get('POST.itemtext') === '') { + $f3->set('error', 'Can not add empty list item'); + $ok = false; + } else { + $l->addItem($f3->get('POST.itemtext')); + } + break; + case 'delitem': + $l->delItem($f3->get('POST.id')); + break; + case 'setchecked': + $it = $l->getItem($f3->get('POST.id')); + if($it === NULL) { + $f3->set('error', 'Invalid item ID'); + $ok = false; + } else { + $it->checked = ($f3->get('POST.checked') === 'y'); + $it->save(); + } + break; + case 'moveup': + $l->moveItem($f3->get('POST.id'), -1); + break; + case 'movedown': + $l->moveItem($f3->get('POST.id'), 1); + break; + case 'movex': + $l->moveItem($f3->get('POST.id'), $f3->get('POST.move')); + break; + case 'delchecked': + $l->deleteChecked(); + break; + default: + $ok = false; + $f3->set('error', 'Unknown list action.'); + } + + if($ok) { + $f3->set('success', 'List updated'); + } + } + + $this->populateListData($f3, $l); + } +} \ No newline at end of file diff --git a/autoload/Todo/Model/TodoList.php b/autoload/Todo/Model/TodoList.php new file mode 100644 index 0000000..4470c34 --- /dev/null +++ b/autoload/Todo/Model/TodoList.php @@ -0,0 +1,100 @@ +load(array('id=?', $id)); + return !$this->dry(); + } + + public function itemsToArray() { + $items = array(); + $it = new \DB\SQL\Mapper($this->db, 'items'); + for($it->load(array('list=?', $this->id), array('order' => 'ord ASC')); !$it->dry(); $it->next()) { + $items[] = array( + 'id' => $it->id, + 'ord' => $it->ord, + 'text' => $it->text, + 'date' => $it->date, + 'checked' => $it->checked, + ); + } + + return $items; + } + + public function countItems() { + $it = new \DB\SQL\Mapper($this->db, 'items'); + return $it->count(array('list=?', $this->id)); + } + + public function getItem($id) { + $it = new \DB\SQL\Mapper($this->db, 'items'); + $it->load(array('list=? AND id=?', $this->id, $id)); + return $it->dry() ? NULL : $it; + } + + public function moveItem($id, $movement) { + $it = new \DB\SQL\Mapper($this->db, 'items'); + $it->load(array('list=? AND id=?', $this->id, $id)); + if($it->dry()) { + return; + } + $ordOld = $it->ord; + $it->reset(); + + $ordNew = min(max(0, $ordOld + $movement), $this->countItems() - 1); + if($ordNew === $ordOld) { + return; + } + + $moveSgn = ($movement > 0) ? 1 : -1; + + if($moveSgn === 1) { + $it->load(array('list=? AND ord>? AND ord<=?', $this->id, $ordOld, $ordNew)); + } else { + $it->load(array('list=? AND ord>=? AND ordid, $ordNew, $ordOld)); + } + for(;!$it->dry(); $it->next()) { + $it->ord -= $moveSgn; + $it->save(); + } + + $it->reset(); + $it->load(array('list=? AND id=?', $this->id, $id)); + $it->ord = $ordNew; + $it->save(); + } + + public function addItem($text) { + $n = $this->countItems(); + + $now = new \DateTime('now'); + + $it = new \DB\SQL\Mapper($this->db, 'items'); + $it->text = $text; + $it->ord = $n; + $it->date = $now->format('Y-m-d H:i:s'); + $it->checked = false; + $it->list = $this->id; + $it->save(); + } + + public function delItem($id) { + $this->db->exec('DELETE FROM `items` WHERE `list` = :l AND `id` = :i', array(':l' => $this->id, ':i' => $id)); + } + + public function deleteList() { + $this->db->exec('DELETE FROM `items` WHERE `list` = :l', array(':l' => $this->id)); + $this->erase(); + } + + public function deleteChecked() { + $this->db->exec('DELETE FROM `items` WHERE `list` = :l AND `checked` = :c', array(':l' => $this->id, ':c' => true)); + } +} \ No newline at end of file diff --git a/autoload/Todo/Model/User.php b/autoload/Todo/Model/User.php new file mode 100644 index 0000000..53c8f67 --- /dev/null +++ b/autoload/Todo/Model/User.php @@ -0,0 +1,66 @@ +code = ''; + for($i = 0; $i < 16; $i++) { + $this->code .= substr($alphabet, mt_rand(0, $len-1), 1); + } + } + + public function register($name, $email, $password) { + $this->load(array('name=? OR email=?', $name, $email)); + if(!$this->dry()) { + $this->reset(); + return false; + } + + $this->name = $name; + $this->email = $email; + $this->pwhash = \Bcrypt::instance()->hash($password); + $this->active = false; + $this->makeCode(); + $this->save(); + return true; + } + + public function byName($name) { + $this->load(array('name=?', $name)); + return !$this->dry(); + } + + public function byID($id) { + $this->load(array('id=?', $id)); + return !$this->dry(); + } + + public function byEmail($email) { + $this->load(array('email=?', $email)); + return !$this->dry(); + } + + public function verifyPass($password) { + return \Bcrypt::instance()->verify($password, $this->pwhash); + } + + public function deleteUser() { + $this->db->exec('DELETE FROM `items` WHERE `list` IN (SELECT `id` FROM `lists` WHERE `user` = :u)', array(':u' => $this->id)); + $this->db->exec('DELETE FROM `lists` WHERE `user` = :u', array(':u' => $this->id)); + $this->erase(); + } + + # Returns a TodoList object that can be iterated with ->next(). + public function lists() { + $l = new \Todo\Model\TodoList($this->db); + $l->load(array('user=?', $this->id), array('order' => 'name ASC')); + return $l; + } +} \ No newline at end of file diff --git a/autoload/Todo/SessionHelper.php b/autoload/Todo/SessionHelper.php new file mode 100644 index 0000000..b4811ab --- /dev/null +++ b/autoload/Todo/SessionHelper.php @@ -0,0 +1,49 @@ +user->lists(); !$l->dry(); $l->next()) { + $lists[] = array( + 'id' => $l->id, + 'name' => $l->name, + 'active' => ($l->id === $f3->get('listid')), + ); + } + $f3->set('lists', $lists); + } + + public function beforeRoute($f3) { + $id = $f3->get('SESSION.userID'); + if(is_numeric($id) && ($id > 0)) { + $this->user = new \Todo\Model\User($f3->get('DB')); + if((!$this->user->byID($id)) || (!$this->user->active)) { + $this->user = NULL; + } + } + $f3->set('listid', -1); + } + + public function afterRoute($f3) { + $f3->set('SESSION.userID', ($this->user === NULL) ? -1 : $this->user->id); + if($this->user !== NULL) { + $f3->set('user', $this->user->name); + $this->populateLists($f3); + } else { + $f3->set('user', ''); + } + } + + protected function needLogin($f3) { + if($this->user === NULL) { + $f3->set('error', 'You must be logged in to do that'); + $f3->set('content', 'blank.html'); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/autoload/Todo/UserManager.php b/autoload/Todo/UserManager.php new file mode 100644 index 0000000..306eb02 --- /dev/null +++ b/autoload/Todo/UserManager.php @@ -0,0 +1,203 @@ +user !== NULL) { + $f3->set('info', 'You are already logged in'); + $f3->set('content', 'blank.html'); + return; + } + $f3->set('content', 'login.html'); + + if($f3->get('VERB') !== "POST") { + return; + } + + $this->user = new \Todo\Model\User($f3->get('DB')); + if($this->user->byName($f3->get('POST.name')) && $this->user->verifyPass($f3->get('POST.password')) && $this->user->active) { + $f3->set('success', 'Login Successful!'); + $f3->set('content', 'noListSelected.html'); + } else { + $this->user = NULL; + $f3->set('error', 'Username or password is wrong or account is not active.'); + } + } + + public function logout($f3, $args) { + $this->user = NULL; + $f3->set('success', 'Logout Successful!'); + $f3->set('content', 'login.html'); + } + + public function register($f3, $args) { + if($this->user !== NULL) { + $f3->set('error', 'You already have an account!'); + $f3->set('content', 'blank.html'); + return; + } + $f3->set('content', 'register.html'); + + if($f3->get('VERB') !== 'POST') { + return; + } + + if(($f3->get('POST.password') === '') || ($f3->get('POST.name') === '') || ($f3->get('POST.email') === '')) { + $f3->set('error', 'All form inputs must be filled out'); + return; + } + + if($f3->get('POST.password') !== $f3->get('POST.repeatPassword')) { + $f3->set('error', 'Passwords do not match'); + return; + } + + $u = new \Todo\Model\User($f3->get('DB')); + if($u->register($f3->get('POST.name'), $f3->get('POST.email'), $f3->get('POST.password'))) { + $f3->set('username', $u->name); + $f3->set('activationurl', 'http://'.$f3->get('HOST').$f3->get('BASE').'/activate/'.$u->id.'/'.$u->code); + mail($u->email, 'Activation code for ' . $f3->get('appname'), \Template::instance()->render('mails/activationcode', 'text/plain'), 'From:'.$f3->get('mail_from')); + + $f3->set('success', 'New account created. Check your mails for the activation link.'); + $f3->set('content', 'blank.html'); + } else { + $f3->set('error', 'Name or E-Mail already in use.'); + } + } + + public function delete($f3, $args) { + if($this->needLogin($f3)) { + return; + } + + $f3->set('content', 'deleteAccount.html'); + + if(($f3->get('VERB') == 'POST') && ($f3->get('POST.confirm') == 'OK')) { + $this->user->deleteUser(); + $this->user = NULL; + $f3->set('success', 'Account deleted!'); + $f3->set('content', 'blank.html'); + } + } + + public function activate($f3, $args) { + $f3->set('content', 'blank.html'); + + $u = new \Todo\Model\User($f3->get('DB')); + if(!$u->byID($args['user'])) { + $f3->set('error', 'Unknown user.'); + return; + } + + if($u->active) { + $f3->set('info', 'Account already activated'); + return; + } + + if($u->code !== $args['code']) { + $f3->set('error', 'Wrong activation code!'); + return; + } + + $u->active = true; + $u->makeCode(); # set a new random code to prevent double usage + $u->save(); + + $f3->set('success', 'Account activated!'); + } + + public function initResetpw($f3, $args) { + $f3->set('content', 'pwresetRequest.html'); + + if($f3->get('VERB') !== 'POST') { + return; + } + + $u = new \Todo\Model\User($f3->get('DB')); + if(!$u->byEmail($f3->get('POST.email'))) { + $f3->set('error', 'No account with this address registered.'); + return; + } + + $u->makeCode(); + $u->save(); + + $f3->set('username', $u->name); + $f3->set('reseturl', 'http://'.$f3->get('HOST').$f3->get('BASE').'/pwreset/'.$u->id.'/'.$u->code); + mail($u->email, 'Password reset for ' . $f3->get('appname'), \Template::instance()->render('mails/pwreset', 'text/plain'), 'From:'.$f3->get('mail_from')); + + $f3->set('success', 'Password reset link was sent to your E-Mail address.'); + $f3->set('content', 'blank.html'); + } + + public function resetpw($f3, $args) { + $u = new \Todo\Model\User($f3->get('DB')); + if((!$u->byID($args['user'])) || ($u->code !== $args['code'])) { + $f3->set('error', 'Invalid password reset link.'); + $f3->set('content', 'blank.html'); + return; + } + + $f3->set('content', 'pwreset.html'); + if($f3->get('VERB') !== 'POST') { + return; + } + + if($f3->get('POST.password') !== $f3->get('POST.repeatPassword')) { + $f3->set('error', 'Passwords do not match'); + return; + } + + $u->pwhash = \Bcrypt::instance()->hash($f3->get('POST.password')); + $u->makeCode(); + $u->save(); + + $f3->set('success', 'Password changed'); + $f3->set('content', 'blank.html'); + } + + public function settings($f3, $args) { + if($this->needLogin($f3)) { + return; + } + + $f3->set('content', 'settings.html'); + $f3->set('email', $this->user->email); + + if($f3->get('VERB') !== 'POST') { + return; + } + + $ok = array(); + $error = array(); + + if(($this->user->email !== $f3->get('POST.email')) && ($f3->get('POST.email') !== '')) { + $this->user->email = $f3->get('POST.email'); + $f3->set('email', $this->user->email); + $ok[] = 'E-Mail address changed.'; + } + + if($f3->get('POST.password') !== '') { + if($f3->get('POST.password') === $f3->get('POST.repeatPassword')) { + $this->user->pwhash = \Bcrypt::instance()->hash($f3->get('POST.password')); + $ok[] = 'Password changed.'; + } else { + $error[] = 'Passwords do not match.'; + } + } + + if(!empty($ok)) { + $this->user->save(); + } + + $text = array_reduce(array_merge($ok, $error), function($a, $b) { return $a . ' ' . $b; }); + if((!empty($ok)) && (!empty($error))) { + $f3->set('info', $text); + } else if(!empty($ok)) { + $f3->set('success', $text); + } else if(!empty($error)) { + $f3->set('error', $text); + } + } +} diff --git a/config.ini b/config.ini new file mode 100755 index 0000000..b1f4e23 --- /dev/null +++ b/config.ini @@ -0,0 +1,7 @@ +[globals] +DEBUG=0 +sql_dsn="mysql:host=localhost;dbname=DATABASE" +sql_user=USER +sql_pass=PASSWORD +mail_from=no-reply@your-domain +appname=Todo diff --git a/index.php b/index.php new file mode 100755 index 0000000..fc9e213 --- /dev/null +++ b/index.php @@ -0,0 +1,41 @@ +set('AUTOLOAD','autoload/'); +$f3->set('UI', 'ui/'); + +$f3->config('config.ini'); + +# Init DB +$db = new DB\SQL($f3->get('sql_dsn'), $f3->get('sql_user'), $f3->get('sql_pass')); +$f3->set("DB", $db); + +# Init Sessions +new Db\SQL\Session($db); + +# Homepage +$f3->route('GET @root: /', 'Todo\Lists->home'); + +# User management stuff +$f3->route('GET|POST @login: /login', 'Todo\UserManager->login'); +$f3->route('GET @logout: /logout', 'Todo\UserManager->logout'); +$f3->route('GET|POST @register: /register', 'Todo\UserManager->register'); +$f3->route('GET|POST @delete_acc: /delete_acc', 'Todo\UserManager->delete'); +$f3->route('GET /activate/@user/@code', 'Todo\UserManager->activate'); +$f3->route('GET|POST @resetpw: /resetpw', 'Todo\UserManager->initResetpw'); +$f3->route('GET|POST /resetpw/@user/@code', 'Todo\UserManager->resetpw'); +$f3->route('GET|POST @settings: /settings', 'Todo\UserManager->settings'); + +# List stuff +$f3->route('POST @newlist: /newlist', 'Todo\Lists->newList'); +$f3->route('GET|POST /list/@list', 'Todo\Lists->showList'); +$f3->route('GET|POST /list/@list/delete', 'Todo\Lists->deleteList'); + +$f3->route('GET /foo', function($f3) { + $f3->set('content', 'blank.html'); + $f3->set('info', 'http://'.$f3->get('HOST').$f3->get('BASE')); +}); + +$f3->run(); +echo Template::instance()->render('master.html'); diff --git a/ui/blank.html b/ui/blank.html new file mode 100644 index 0000000..e69de29 diff --git a/ui/deleteAccount.html b/ui/deleteAccount.html new file mode 100644 index 0000000..565082f --- /dev/null +++ b/ui/deleteAccount.html @@ -0,0 +1,6 @@ +
+ +

Do you really want to delete your account?

+

No

+

+
\ No newline at end of file diff --git a/ui/deleteList.html b/ui/deleteList.html new file mode 100644 index 0000000..a71af7f --- /dev/null +++ b/ui/deleteList.html @@ -0,0 +1,8 @@ +
+ +

Do you really want to delete the list "{{ @listname }}"?

+

+ No + +

+
\ No newline at end of file diff --git a/ui/img/sprites.png b/ui/img/sprites.png new file mode 100644 index 0000000..b3af1e4 Binary files /dev/null and b/ui/img/sprites.png differ diff --git a/ui/img/sprites.xcf b/ui/img/sprites.xcf new file mode 100644 index 0000000..830a6a5 Binary files /dev/null and b/ui/img/sprites.xcf differ diff --git a/ui/list.html b/ui/list.html new file mode 100644 index 0000000..ce998dd --- /dev/null +++ b/ui/list.html @@ -0,0 +1,46 @@ + +
+

+ + + +

+
+ + +
List is empty
+ +
    + + +
    + + + + {{ @it.text }} + + + + + + + +
    + +
    +
+
+
+ +
+
+
+
+ +
+
+ + + +
+
\ No newline at end of file diff --git a/ui/login.html b/ui/login.html new file mode 100644 index 0000000..0b8d2e4 --- /dev/null +++ b/ui/login.html @@ -0,0 +1,8 @@ +

Login

+ +
+

+

+

+
+

Forgot Password? | Register a new account

\ No newline at end of file diff --git a/ui/mails/activationcode b/ui/mails/activationcode new file mode 100644 index 0000000..bb46bfd --- /dev/null +++ b/ui/mails/activationcode @@ -0,0 +1,6 @@ +Hi, {{ @username }}! + +You have registered an account for {{ @appname }}. +To activate the account, visit this URL: + +{{ @activationurl }} \ No newline at end of file diff --git a/ui/mails/pwreset b/ui/mails/pwreset new file mode 100644 index 0000000..2c69c37 --- /dev/null +++ b/ui/mails/pwreset @@ -0,0 +1,11 @@ +Hi, {{ @username }}! + +You have requested a password reset four your account on +{{ @appname }}. + +To reset you password, use this URL: + +{{ @reseturl }} + +If you did NOT request a password reset, you can simply ignore this +email. diff --git a/ui/master.html b/ui/master.html new file mode 100644 index 0000000..25775f9 --- /dev/null +++ b/ui/master.html @@ -0,0 +1,63 @@ + + + + Todo + + + +
+ +
+
+ +
    + + + + + {{ @l.name }} + X + + + + +
  • No lists here. Why not add one below?
  • +
    +
    +
+ +
+ +
+
+
+ +
+ +
{{ @success }}
+
+ +
{{ @error }}
+
+ +
{{ @info }}
+
+ + +
+ + \ No newline at end of file diff --git a/ui/noListSelected.html b/ui/noListSelected.html new file mode 100644 index 0000000..053b8e3 --- /dev/null +++ b/ui/noListSelected.html @@ -0,0 +1 @@ +
No list selected
\ No newline at end of file diff --git a/ui/pwreset.html b/ui/pwreset.html new file mode 100644 index 0000000..8d563d0 --- /dev/null +++ b/ui/pwreset.html @@ -0,0 +1,7 @@ +

Password reset

+ +
+

New Password:

+

Retype Password:

+

+
\ No newline at end of file diff --git a/ui/pwresetRequest.html b/ui/pwresetRequest.html new file mode 100644 index 0000000..a6c51b3 --- /dev/null +++ b/ui/pwresetRequest.html @@ -0,0 +1,6 @@ +

Password reset

+ +
+

+

+
\ No newline at end of file diff --git a/ui/register.html b/ui/register.html new file mode 100644 index 0000000..8686600 --- /dev/null +++ b/ui/register.html @@ -0,0 +1,10 @@ +

Register

+ +
+

+

+

+

+

+
+

Already have an account? Go to Login.

\ No newline at end of file diff --git a/ui/settings.html b/ui/settings.html new file mode 100644 index 0000000..47f3b1b --- /dev/null +++ b/ui/settings.html @@ -0,0 +1,8 @@ +

Account Settings

+ +
+

+

+

+

+
\ No newline at end of file diff --git a/ui/style.css b/ui/style.css new file mode 100644 index 0000000..18ae794 --- /dev/null +++ b/ui/style.css @@ -0,0 +1,306 @@ +@import url("DejaVu_Sans_ExtraLight/stylesheet.css"); +* { + font-family: sans-serif; +} +html, body { + padding: 0px; + margin: 0px; +} +body { + background: white; + font-size: 12pt; +} + +input[type="text"], input[type="password"] { + background: #fff; + border: 1px solid #666; + font-size: 12pt; + padding: 0.5ex 0ex 0.5ex; +} +input[type="text"]:hover, input[type="password"]:hover { + border-color: #000; +} +button, .fakebutton { + background: #eee; + border: 1px solid #ccc; + font-size: 12pt; + padding: 0.5ex 0ex 0.5ex; + cursor: pointer; +} +button:hover, .fakebutton:hover { + background-color: #ccc !important; +} +a.fakebutton { + color: black; + text-decoration: none; + display: inline-block; +} + +#headerwrap { + position: fixed; + top: 0px; + width: 100%; + background: #aaa; + color: #eee; + margin: 0px; + z-index: 1000; +} + +#header { + margin: 0px 7mm 0px; +} + +#header a { + display: inline-block; + color: #eee; + text-decoration: none; + border-bottom: 3px solid #bbb; + height: 100%; + vertical-align: middle; + padding: 0.5ex 0ex 0.5ex; +} +#header a:hover { + color: #fff; + border-bottom-color: #55c5ff !important; +} + +#userinfo { + position: absolute; + right: 7mm; +} +.username { + color: white; +} + +.bghelp { + z-index: -1; + background: #eee; + position: fixed; + margin: 0; + padding: 0; + top: 0; + bottom: 0; + left: 0; + width: 60mm; +} + +#lists { + position: absolute; + left: 0; + top: 0; + width: 60mm; + z-index: 1; + padding: 0mm; + background: #eee; +} + +#lists ul { + list-style: none; + margin: 3.5ex 0mm 3em; + padding: 0px; +} + +#lists ul li { + position: relative; + margin: 0; + padding: 0.5ex 3ex 0.5ex 1ex; +} +#lists ul li:hover { + background: #f8f8f8; +} +#lists ul li.active { + background: #fff; +} +#lists a { + color: #555; + text-decoration: none; +} +#lists a:hover { + color: black; +} +.listlink { + display: block; +} + + +.deletelist { + display: inline-block; + margin:0; + position: absolute; + top: 0; + bottom: 0; + right: 0; + width: 3ex; + border-left: 1px dotted #555; + text-align: center; + padding: 0.5ex 0ex 0.5ex 0ex; +} + +.deletelist:hover { + background: #ccc; +} + +#addlist { + position: fixed; + bottom: 0px; + width: 58mm; + background: #ddd; + padding: 1mm; + vertical-align: middle; +} +#addlist button { + position: absolute; + right: 1mm; + width: 7mm; + margin: 0mm; +} +#addlist input { + width: 100%; + margin: 0mm; + width: 50mm; +} + +#content { + padding: 4ex 2mm 2mm 63mm; +} + +h1 { + font-family: "DejaVu Sans ExtraLight", sans-serif; + font-weight: bold; + font-size: 16pt; +} + +h1 input.listname { + border: 1px solid #ccc; + font-size: 16pt; + margin: 0; + font-family: "DejaVu Sans ExtraLight", sans-serif; + font-weight: bold; +} +h1 input.listname:hover, h1 input.listname:focus { + border: 1px solid black; +} +h1 button { + background-color: #f7f7f7; + color: #ddd; + border: 1px solid #ddd; +} +h1:hover button { + background: #eee; + border: 1px solid #ccc; + color: black; +} + +ul.todolist { + list-style: none; + margin: 0; + padding: 0; +} +ul.todolist li { + margin: 0; + position: relative; + padding: 0.6ex 0 0.6ex; + border-top: 1px solid #f7f7f7; + min-height: 34px +} +ul.todolist li:first-child { + border-top: none; +} +ul.todolist form { + display: inline; +} +li.checked span.txt { + font-style: italic; + color: gray; + text-decoration: line-through; + vertical-align: middle; +} +ul.todolist span.btncluster { + vertical-align: middle; + position: absolute; + right: 0; +} +ul.todolist span.btncluster button { + background: #fbfbfb; + border: 1px solid #f2f2f2; + color: #bbb; +} +ul.todolist li:hover span.btncluster button { + background: #eee; + border: 1px solid #ccc; + color: black; +} + +.chkbtn { + padding: 0; + cursor: pointer; + width: 20px; + height: 20px; + vertical-align: middle; +} +.chkbtn.checked { + background-image: url(img/sprites.png); +} +.chkbtn input { + display: none; +} + +.notify { + margin: -100px auto 1mm; + width: 50%; + text-align: center; + color: white; + padding: 100px 5px 5px 35px; + position: relative; +} + +.notify .icon { + display: block; + width: 20px; + height: 100%; + position: absolute; + left: 5px; + top: 45px; + background-size: 80px 20px; + background-repeat: no-repeat; + background-image: url(img/sprites.png); +} + +.success {background: #2a0;} +.success .icon {background-position: left -20px top 50%;} +.info {background: #48f;} +.info .icon {background-position: left -40px top 50%;} +.error {background: #f40;} +.error .icon {background-position: left -60px top 50%;} + +.delchecked { + text-align: right; +} + +.empty { + font-style: italic; + margin: 2mm; +} +.nolist { + font-style: italic; + font-size: 18pt; + color: #999; + text-align: center; + position: absolute; + top: 40%; +} + +form.tabform { + display: table; +} +form.tabform p { + display: table-row; +} +form.tabform input, form.tabform label { + display: table-cell; + margin: 1mm; +} +form.tabform p.formctrl { + display: block; + margin: 0; +} diff --git a/ui/welcome.html b/ui/welcome.html new file mode 100644 index 0000000..3b110bb --- /dev/null +++ b/ui/welcome.html @@ -0,0 +1,3 @@ +

Welcome

+ +

Foo!

\ No newline at end of file -- cgit v1.2.3-54-g00ecf