diff --git a/BookmarkManager.php b/BookmarkManager.php index 725e9e5..aa476b1 100644 --- a/BookmarkManager.php +++ b/BookmarkManager.php @@ -6,6 +6,7 @@ class BookmarkManager { + protected $config; protected $db; protected $user; protected $baseDir; @@ -14,13 +15,13 @@ class BookmarkManager public function __construct($requestUri = '/') { - $config = require __DIR__.'/config.php'; + $this->config = require __DIR__.'/config.php'; - if (empty($config['baseDir'])) { + if (empty($this->config['baseDir'])) { throw new \Exception('baseDir not defined in config.php'); } - $this->baseDir = $config['baseDir']; + $this->baseDir = $this->config['baseDir']; if (!is_dir($this->baseDir)) { throw new \Exception('invalid baseDir in config.php'); @@ -32,11 +33,11 @@ public function __construct($requestUri = '/') $this->baseDir = rtrim($this->baseDir, '/').'/'; - if (empty($config['baseUri'])) { + if (empty($this->config['baseUri'])) { throw new \Exception('baseUri not defined in config.php'); } - $baseUri = rtrim($config['baseUri'], '/').'/'; + $baseUri = rtrim($this->config['baseUri'], '/').'/'; if (strpos($requestUri, $baseUri) !== 0) { throw new \Exception('invalid baseuri'); @@ -75,6 +76,15 @@ public function __construct($requestUri = '/') $this->db = new DB($this->baseDir.$user.'/b.db'); } + public function getConfig($key) + { + if (isset($this->config[$key])) { + return $this->config[$key]; + } + + return false; + } + public function getDB() { return $this->db; @@ -145,8 +155,8 @@ public function handleAjaxRequest($postData) $result['result'] = true; } - header('Content-Type: application/json'); - echo json_encode($result, JSON_FORCE_OBJECT); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($result, JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE); exit(); } diff --git a/DB.php b/DB.php index 9cd903f..c8944a5 100644 --- a/DB.php +++ b/DB.php @@ -55,19 +55,33 @@ public function exists($link) return (bool) $st->fetch(); } - public function getEntries($filter = false) + public function getEntries($filter = false, $skip = false, $count = false) { if (!$filter) { $filter = '%'; } - $st = $this->pdo->prepare(' + if ($skip !== false && $count !== false) { + $limit = 'LIMIT :skip, :count'; + } else { + $limit = ''; + } + + $st = $this->pdo->prepare(" SELECT id, desc, link FROM b WHERE desc LIKE :filter ORDER BY date DESC - '); + $limit + "); + + $args = [ ':filter' => "%$filter%" ]; + + if ($skip !== false && $count !== false) { + $args['skip'] = $skip; + $args['count'] = $count; + } - $st->execute([ ':filter' => "%$filter%" ]); + $st->execute($args); $ret = $st->fetchAll(); diff --git a/LICENSE b/LICENSE index 484b4ca..5e1f299 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2011-2016 Sebastian Volland +Copyright (c) 2011-2017 Sebastian Volland Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 24206da..3e6bc8c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # b - Bookmark manager -b is a minimalistic bookmark manager for your own server written in PHP where bookmarks are stored in a sqlite database. +b is a minimalistic bookmark manager for your own server. Written in PHP. +Bookmarks are stored in a sqlite database. Features: + + * filtering + * tagging + * automatic fetching of page title + * infinite scrolling (optional) + * bookmarklet + * multiple users ### Requirements @@ -10,15 +18,23 @@ b is a minimalistic bookmark manager for your own server written in PHP where bo ### Configuration instructions - * Copy all repository files to a directory accessible by the webserver-user, like `/var/www/b` + * Copy all repository files to a directory accessible by the webserver-user, + like `/var/www/b` * Move `config.template.php` to `config.php` and edit it - * `baseDir` is the directory where the sqlite dbs are stored, e.g. `/var/bookmarks/`. The directory must be readable and writeable by the webserver-user - * `baseUri` is the base uri's relative path param, e.g. `/b/` if the website is accessible via `http://example.com/b/`. Or use `/` for `http://bookmarks.example.com/` for example, if you want to have a dedicated subdomain for the service. - * Create a new user-account simply by creating a new directory in `baseDir`: `mkdir /var/bookmarks/peter/` + * `baseDir` is the directory where the sqlite dbs are stored, e.g. + `/var/bookmarks/`. The directory must be readable and writeable by the + webserver-user + * `baseUri` is the base uri's relative path param, e.g. `/b/` if the website + is accessible via `http://example.com/b/`. Or use `/` for + `http://bookmarks.example.com/` for example, if you want to have a + dedicated subdomain for the service. + * Create a new user-account simply by creating a new directory in `baseDir`: + `mkdir /var/bookmarks/peter/` #### Webserver configuration example for apache - * If you want your bookmarks to be accessible under their own subdomain like `http://bookmarks.example.com/`, add a virtual host to your `httpd.conf`: + * If you want your bookmarks to be accessible under their own subdomain like + `http://bookmarks.example.com/`, add a virtual host to your `httpd.conf`: ServerName bookmarks.example.com @@ -39,32 +55,54 @@ b is a minimalistic bookmark manager for your own server written in PHP where bo - * Create a password file and restart apache. If you don't want to use a password, remove the "Password protection" part from the virtual host configuration. + * Create a password file and restart apache. If you don't want to use a + password, remove the "Password protection" part from the virtual host + configuration. htpasswd -c /opt/bookmarks/htusers peter apachectl restart - * If you want to use a localhost fake domain, add the host to your `/etc/hosts` file: + * If you want to use a localhost fake domain, add the host to your + `/etc/hosts` file: 127.0.0.1 bookmarks.example.com - * Peter's bookmarks should now be accessible via `http://bookmarks.example.com/peter` - * Run a configuration-check via `http://bookmarks.example.com/index.php?configtest` + * Peter's bookmarks should now be accessible via + `http://bookmarks.example.com/peter` + * Run a configuration-check via + `http://bookmarks.example.com/index.php?configtest` ### How to use - * To add a new bookmark, simply paste it into the input field and press return. the url may be followed by hash tags, e.g. `http://example.com #example #bla #wurst` - * The website's title is automatically fetched and the bookmark is added to the database. - * Edit title by double clicking it. This opens a prompt-dialog where you can edit the title. Enter '-' (minus sign) to remove an entry. + * To add a new bookmark, simply paste it into the input field and press + return. the url may be followed by hash tags, e.g. `http://example.com + #example #bla #wurst` + * The website's title is automatically fetched and the bookmark is added to + the database. + * Edit title by double clicking it. This opens a prompt-dialog where you can + edit the title. Enter '-' (minus sign) to remove an entry. * To edit the URL, double click beside the link. - * The input field can also be used to filter bookmarks. Filtering is done with a full-text search on all titles. + * The input field can also be used to filter bookmarks. Filtering is done with + a full-text search on all titles. + +### Infinite scrolling + +If you have a massive amount of bookmarks and you don't want to load them all at +once, you can activate infinite scrolling. This will load a limited amount on +bookmarks initially and load more when you scroll to the bottom of the page. +Activate infinite scrolling by adding `'infiniteScrolling' => 200` to your +`config.php`. Replace `200` with the number of bookmarks you want to load each +time you hit the bottom. ### Bookmarklet -Visit `/[user]/bookmarklet` to access the user's bookmarklet, e.g. `http://bookmarks.example.com/peter/bookmarklet`. (Thanks to nibreh for the suggestion!) +Visit `/[user]/bookmarklet` to access the user's bookmarklet, e.g. +`http://bookmarks.example.com/peter/bookmarklet`. (Thanks to nibreh for the +suggestion!) ### Credits -Copyright (c) 2011-2016 Sebastian Volland http://github.com/sebcode +Copyright (c) 2011-2017 Sebastian Volland http://github.com/sebcode -The source code is licensed under the terms of the MIT license (see LICENSE file). +The source code is licensed under the terms of the MIT license (see LICENSE +file). diff --git a/bookmarkManager.js b/bookmarkManager.js index f1d6912..cd1d3be 100644 --- a/bookmarkManager.js +++ b/bookmarkManager.js @@ -147,6 +147,43 @@ return false }) + + let loadingMore = false + let ifStep = window.infiniteScrolling + let ifSkip = ifStep + + async function loadMore() { + if (loadingMore) { + return + } + + loadingMore = true + + const url = + `?filter=${encodeURIComponent(window.filter)}` + + `&format=html&count=${ifStep}&skip=${ifSkip}` + const res = await fetch(url) + const text = await res.text() + + if (!text) { + return + } + + contentEl.insertAdjacentHTML('beforeend', text) + ifSkip += ifStep + loadingMore = false + } + + if (window.infiniteScrolling) { + window.addEventListener('scroll', e => { + const offset = + document.body.offsetHeight - (window.pageYOffset + window.innerHeight) + + if (offset < 500) { + loadMore() + } + }) + } })() // vim: et ts=2 sw=2 sts=2 diff --git a/config.template.php b/config.template.php index 8071321..0233b84 100644 --- a/config.template.php +++ b/config.template.php @@ -2,5 +2,8 @@ return [ 'baseDir' => '/opt/bookmarks/', - 'baseUri' => '/' + 'baseUri' => '/', + /* Optional: Infinite scrolling. Value is the number of items to load + * on each increment. */ + //'infiniteScrolling' => 200, ]; diff --git a/index.php b/index.php index 4032f82..ad540d4 100644 --- a/index.php +++ b/index.php @@ -50,18 +50,64 @@ $filter = false; } - $entries = $b->getDB()->getEntries($filter); + $skip = false; + $count = $b->getConfig('infiniteScrolling'); + if ($count !== false) { + $skip = 0; + } + + if (isset($_GET['skip'])) { + $skip = $_GET['skip']; + } + + if (isset($_GET['count'])) { + $count = $_GET['count']; + } + + if (!empty($_GET['format'])) { + $format = $_GET['format']; + } else { + $format = false; + } + + $entries = $b->getDB()->getEntries($filter, $skip, $count); + + if ($format === 'json') { + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($entries, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + exit(); + } + + if ($format === 'html') { + dumpEntries($entries); + exit(); + } } catch (\Exception $e) { - if ($e->getCode()) { - http_response_code($e->getCode()); + if (($code = $e->getCode()) && is_numeric($code)) { + http_response_code($code); } else { http_response_code(500); } echo $e->getMessage(); + throw $e; exit(); } +function dumpEntries($entries) +{ + foreach ($entries as $entry) { + ?> +
+
+ +
+
+ + @@ -85,18 +131,17 @@ - + -
-
- -
- - - +