Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/bookmarks.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Klehr <mklehr@gmx.net>2018-07-21 21:39:44 +0300
committerGitHub <noreply@github.com>2018-07-21 21:39:44 +0300
commit0339763f280f62655238db1f41c0dc43b55b2dd1 (patch)
treea2435d807024db25fa9c930dcf80a6dff7deade7
parent8531ef7f4b85f7a6c6df0b3ec511e4ac0d9a171c (diff)
parente42fbf7073558d8f82cef5b4d77923ead615ca5c (diff)
Merge pull request #531 from nextcloud/feature/screenly-screenshotsv0.12.0-rc3
Overhaul previews
-rw-r--r--appinfo/application.php35
-rw-r--r--appinfo/database.xml14
-rw-r--r--appinfo/info.xml11
-rw-r--r--composer.json3
-rw-r--r--composer.lock52
-rw-r--r--controller/lib/bookmarks.php99
-rw-r--r--controller/lib/linkexplorer.php10
-rw-r--r--controller/lib/previews/defaultpreviewservice.php (renamed from controller/lib/imageservice.php)67
-rw-r--r--controller/lib/previews/faviconpreviewservice.php (renamed from controller/lib/faviconservice.php)34
-rw-r--r--controller/lib/previews/ipreviewservice.php9
-rw-r--r--controller/lib/previews/screenlypreviewservice.php132
-rw-r--r--controller/rest/internalbookmarkcontroller.php47
-rw-r--r--css/bookmarks.css26
-rw-r--r--img/bookmarks-black.svg68
-rw-r--r--js/admin.js36
-rw-r--r--js/models/Bookmark.js24
-rw-r--r--js/templates/BookmarkCard.html2
-rw-r--r--js/templates/BookmarkDetail.html1
-rw-r--r--js/views/BookmarkCard.js73
-rw-r--r--js/views/BookmarkDetail.js7
-rw-r--r--settings/adminsection.php47
-rw-r--r--settings/adminsettings.php57
-rw-r--r--templates/admin.php30
23 files changed, 693 insertions, 191 deletions
diff --git a/appinfo/application.php b/appinfo/application.php
index 4e480107..dd003cd4 100644
--- a/appinfo/application.php
+++ b/appinfo/application.php
@@ -14,8 +14,9 @@
namespace OCA\Bookmarks\AppInfo;
use OCA\Bookmarks\Controller\Lib\Bookmarks;
-use OCA\Bookmarks\Controller\Lib\ImageService;
-use OCA\Bookmarks\Controller\Lib\FaviconService;
+use OCA\Bookmarks\Controller\Lib\Previews\DefaultPreviewService;
+use OCA\Bookmarks\Controller\Lib\Previews\ScreenlyPreviewService;
+use OCA\Bookmarks\Controller\Lib\Previews\FaviconPreviewService;
use \OCP\AppFramework\App;
use OCP\AppFramework\Utility\ITimeFactory;
use \OCP\IContainer;
@@ -29,8 +30,7 @@ use OCA\Bookmarks\Controller\Rest\SettingsController;
use OCP\IUser;
class Application extends App {
-
- public function __construct(array $urlParams = array()) {
+ public function __construct(array $urlParams = []) {
parent::__construct('bookmarks', $urlParams);
$container = $this->getContainer();
@@ -39,7 +39,7 @@ class Application extends App {
* Controllers
* @param IContainer $c The Container instance that handles the request
*/
- $container->registerService('WebViewController', function($c) {
+ $container->registerService('WebViewController', function ($c) {
/** @var IUser|null $user */
$user = $c->query('ServerContainer')->getUserSession()->getUser();
$uid = is_null($user) ? null : $user->getUID();
@@ -55,7 +55,7 @@ class Application extends App {
);
});
- $container->registerService('BookmarkController', function($c) {
+ $container->registerService('BookmarkController', function ($c) {
/** @var IContainer $c */
$user = $c->query('ServerContainer')->getUserSession()->getUser();
$uid = is_null($user) ? null : $user->getUID();
@@ -70,7 +70,7 @@ class Application extends App {
);
});
- $container->registerService('InternalBookmarkController', function($c) {
+ $container->registerService('InternalBookmarkController', function ($c) {
/** @var IContainer $c */
$user = $c->query('ServerContainer')->getUserSession()->getUser();
$uid = is_null($user) ? null : $user->getUID();
@@ -83,13 +83,14 @@ class Application extends App {
$c->query('ServerContainer')->getL10NFactory()->get('bookmarks'),
$c->query('ServerContainer')->query(Bookmarks::class),
$c->query('ServerContainer')->getUserManager(),
- $c->query('ServerContainer')->query(ImageService::class),
- $c->query('ServerContainer')->query(FaviconService::class),
+ $c->query('ServerContainer')->query(DefaultPreviewService::class),
+ $c->query('ServerContainer')->query(FaviconPreviewService::class),
+ $c->query('ServerContainer')->query(ScreenlyPreviewService::class),
$c->query('ServerContainer')->query(ITimeFactory::class)
);
});
- $container->registerService('TagsController', function($c) {
+ $container->registerService('TagsController', function ($c) {
/** @var IContainer $c */
$user = $c->query('ServerContainer')->getUserSession()->getUser();
$uid = is_null($user) ? null : $user->getUID();
@@ -101,7 +102,7 @@ class Application extends App {
);
});
- $container->registerService('InternalTagsController', function($c) {
+ $container->registerService('InternalTagsController', function ($c) {
/** @var IContainer $c */
$user = $c->query('ServerContainer')->getUserSession()->getUser();
$uid = is_null($user) ? null : $user->getUID();
@@ -113,7 +114,7 @@ class Application extends App {
);
});
- $container->registerService('PublicController', function($c) {
+ $container->registerService('PublicController', function ($c) {
/** @var IContainer $c */
$user = $c->query('ServerContainer')->getUserSession()->getUser();
$uid = is_null($user) ? null : $user->getUID();
@@ -126,7 +127,7 @@ class Application extends App {
);
});
- $container->registerService('SettingsController', function($c) {
+ $container->registerService('SettingsController', function ($c) {
/** @var IContainer $c */
$user = $c->query('ServerContainer')->getUserSession()->getUser();
$uid = is_null($user) ? null : $user->getUID();
@@ -138,15 +139,13 @@ class Application extends App {
);
});
- $container->registerService('RecreateAllBookmarks', function($c) {
- /** @var IContainer $c*/
- return new RecreateAllBookmarks(
+ $container->registerService('RecreateAllBookmarks', function ($c) {
+ /** @var IContainer $c*/
+ return new RecreateAllBookmarks(
$c->query('ServerContainer')->getDb(),
$c->query('ServerContainer')->query(Bookmarks::class),
$c->query('ServerContainer')->getConfig()
);
});
-
}
-
}
diff --git a/appinfo/database.xml b/appinfo/database.xml
index 86e835e1..218a0f40 100644
--- a/appinfo/database.xml
+++ b/appinfo/database.xml
@@ -73,20 +73,6 @@
<unsigned>true</unsigned>
<length>4</length>
</field>
- <field>
- <name>image</name>
- <type>text</type>
- <default></default>
- <notnull>false</notnull>
- <length>512</length>
- </field>
- <field>
- <name>favicon</name>
- <type>text</type>
- <default></default>
- <notnull>false</notnull>
- <length>512</length>
- </field>
</declaration>
</table>
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 286a454d..fdcd296c 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -8,7 +8,7 @@
This app allows you to store and organize your favorite places on the web in one spot, while allowing you to sync them with your various devices and browsers.
Check out the third-party clients listed here: https://github.com/nextcloud/bookmarks#third-party-clients
]]></description>
- <version>0.12.0-rc2</version>
+ <version>0.12.0-rc3</version>
<licence>agpl</licence>
<author mail="blizzz@arthur-schiwon.de" homepage="https://www.arthur-schiwon.de">Arthur Schiwon</author>
<author mail="mklehr@gmx.net">Marcel Klehr</author>
@@ -22,9 +22,8 @@ Check out the third-party clients listed here: https://github.com/nextcloud/book
<dependencies>
<nextcloud min-version="12" max-version="14" />
</dependencies>
- <repair-steps>
- <pre-migration>
- <step>OCA\Bookmarks\Migration\RecreateAllBookmarks</step>
- </pre-migration>
- </repair-steps>
+ <settings>
+ <admin>OCA\Bookmarks\Settings\AdminSettings</admin>
+ <admin-section>OCA\Bookmarks\Settings\AdminSection</admin-section>
+ </settings>
</info>
diff --git a/composer.json b/composer.json
index eea3d4e7..f694d285 100644
--- a/composer.json
+++ b/composer.json
@@ -3,6 +3,7 @@
"marcelklehr/link-preview": "^2",
"glenscott/url-normalizer": "^1.4",
"doctrine/inflector": "1.1.*",
- "pguardiario/phpuri": "1.0.*"
+ "pguardiario/phpuri": "1.0.*",
+ "wnx/screeenly-client": "~1.0"
}
}
diff --git a/composer.lock b/composer.lock
index fc1ba6fd..3fa5f405 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "content-hash": "2b2e4153d174a8f74c090cdd2589746e",
+ "content-hash": "b4f3be71a31dd235a4c7f8dda5a556a1",
"packages": [
{
"name": "doctrine/inflector",
@@ -757,6 +757,56 @@
"shim"
],
"time": "2018-04-26T10:06:28+00:00"
+ },
+ {
+ "name": "wnx/screeenly-client",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/stefanzweifel/ScreeenlyClient.git",
+ "reference": "52fef5a2363b25e0fb654fb026f901da969a5228"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/stefanzweifel/ScreeenlyClient/zipball/52fef5a2363b25e0fb654fb026f901da969a5228",
+ "reference": "52fef5a2363b25e0fb654fb026f901da969a5228",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/guzzle": "~5",
+ "illuminate/support": "~5.0",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.4.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Wnx\\ScreeenlyClient": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Stefan Zweifel",
+ "email": "hello@stefanzweifel.io",
+ "homepage": "http://stefanzweifel.io",
+ "role": "Developer & Maintainer"
+ }
+ ],
+ "description": "PHP API Wrapper for screeenly.com",
+ "homepage": "https://github.com/stefanzweifel/ScreeenlyClient",
+ "keywords": [
+ "api",
+ "laravel",
+ "screeenly",
+ "screenshot"
+ ],
+ "time": "2015-02-13T18:57:29+00:00"
}
],
"packages-dev": [],
diff --git a/controller/lib/bookmarks.php b/controller/lib/bookmarks.php
index 641fd544..3cdac6d1 100644
--- a/controller/lib/bookmarks.php
+++ b/controller/lib/bookmarks.php
@@ -43,7 +43,7 @@ class Bookmarks {
/** @var IL10N */
private $l;
- /** @var LinkExplorer */
+ /** @var LinkExplorer */
private $linkExplorer;
/** @var EventDispatcherInterface */
@@ -59,7 +59,7 @@ class Bookmarks {
IDBConnection $db,
IConfig $config,
IL10N $l,
- LinkExplorer $linkExplorer,
+ LinkExplorer $linkExplorer,
UrlNormalizer $urlNormalizer,
EventDispatcherInterface $eventDispatcher,
ILogger $logger
@@ -87,7 +87,7 @@ class Bookmarks {
->select('t.tag')
->selectAlias($qb->createFunction('COUNT(' . $qb->getColumnName('t.bookmark_id') . ')'), 'nbr')
->from('bookmarks_tags', 't')
- ->innerJoin('t','bookmarks','b', $qb->expr()->eq('b.id', 't.bookmark_id'))
+ ->innerJoin('t', 'bookmarks', 'b', $qb->expr()->eq('b.id', 't.bookmark_id'))
->where($qb->expr()->eq('b.user_id', $qb->createNamedParameter($userId)));
if (!empty($filterTags)) {
$qb->andWhere($qb->expr()->notIn('t.tag', array_map([$qb, 'createNamedParameter'], $filterTags)));
@@ -181,11 +181,11 @@ class Bookmarks {
) {
$dbType = $this->config->getSystemValue('dbtype', 'sqlite');
if (is_string($filters)) {
- $filters = array($filters);
+ $filters = [$filters];
}
- $tableAttributes = array('id', 'url', 'title', 'user_id', 'description',
- 'public', 'added', 'lastmodified', 'clickcount', 'image', 'favicon');
+ $tableAttributes = ['id', 'url', 'title', 'user_id', 'description',
+ 'public', 'added', 'lastmodified', 'clickcount'];
$returnTags = true;
@@ -200,17 +200,17 @@ class Bookmarks {
}
$selectedAttributes = array_intersect($tableAttributes, $requestedAttributes);
$qb->select($selectedAttributes);
- }else{
+ } else {
$selectedAttributes = $tableAttributes;
}
$qb->select($selectedAttributes);
if ($dbType == 'pgsql') {
$qb->selectAlias($qb->createFunction("array_to_string(array_agg(" . $qb->getColumnName('t.tag') . "), ',')"), 'tags');
- }else{
+ } else {
$qb->selectAlias($qb->createFunction('GROUP_CONCAT(' . $qb->getColumnName('t.tag') . ')'), 'tags');
}
-
+
if (!in_array($sqlSortColumn, $tableAttributes)) {
$sqlSortColumn = 'lastmodified';
}
@@ -228,7 +228,7 @@ class Bookmarks {
if ($untagged) {
if ($dbType == 'pgsql') {
$tagCol = $qb->createFunction("array_to_string(array_agg(" . $qb->getColumnName('t.tag') . "), ',')");
- }else{
+ } else {
$tagCol = 'tags';
}
$qb->having($qb->expr()->orX(
@@ -252,11 +252,11 @@ class Bookmarks {
}
}
$results = $qb->execute()->fetchAll();
- $bookmarks = array();
+ $bookmarks = [];
foreach ($results as $result) {
if ($returnTags) {
// pgsql returns "", others null
- if($result['tags'] === null || $result['tags'] === '') {
+ if ($result['tags'] === null || $result['tags'] === '') {
$result['tags'] = [];
} else {
$result['tags'] = explode(',', $result['tags']);
@@ -288,14 +288,14 @@ class Bookmarks {
$otherColumns = ['b.url', 'b.title', 'b.description'];
$i = 0;
foreach ($filters as $filter) {
- $expr = [];
+ $expr = [];
if ($dbType == 'pgsql') {
$expr[] = $qb->expr()->iLike(
// Postgres doesn't like select aliases in HAVING clauses, well f*** you too!
$qb->createFunction("array_to_string(array_agg(" . $qb->getColumnName('t.tag') . "), ',')"),
$qb->createPositionalParameter('%'.$this->db->escapeLikeParameter($filter).'%')
);
- }else{
+ } else {
$expr[] = $qb->expr()->iLike('tags', $qb->createPositionalParameter('%'.$this->db->escapeLikeParameter($filter).'%'));
}
if (!$filterTagOnly) {
@@ -307,11 +307,11 @@ class Bookmarks {
}
}
$filterExpressions[] = call_user_func_array([$qb->expr(), 'orX'], $expr);
- $i++;
+ $i++;
}
if ($connectWord == 'AND') {
$filterExpression = call_user_func_array([$qb->expr(), 'andX'], $filterExpressions);
- }else {
+ } else {
$filterExpression = call_user_func_array([$qb->expr(), 'orX'], $filterExpressions);
}
$qb->having($filterExpression);
@@ -444,7 +444,6 @@ class Bookmarks {
* @return null
*/
public function editBookmark($userid, $id, $url, $title, $tags = [], $description = '', $isPublic = false) {
-
$isPublic = $isPublic ? 1 : 0;
// normalize url
@@ -501,10 +500,10 @@ class Bookmarks {
* @param string $image URL to a visual representation of the bookmarked site
* @return int The id of the bookmark created
*/
- public function addBookmark($userid, $url, $title, $tags = array(), $description = '', $isPublic = false, $image = null, $favicon = null) {
+ public function addBookmark($userid, $url, $title, $tags = [], $description = '', $isPublic = false, $image = null, $favicon = null) {
$public = $isPublic ? 1 : 0;
- // do some meta tag inspection of the link...
+ // do some meta tag inspection of the link...
// allow only http(s) and (s)ftp
$protocols = '/^(https?|s?ftp)\:\/\//i';
@@ -513,10 +512,10 @@ class Bookmarks {
$data = $this->getURLMetadata($url);
} else {
// if no allowed protocol is given, evaluate https and https
- foreach(['https://', 'http://'] as $protocol) {
+ foreach (['https://', 'http://'] as $protocol) {
$testUrl = $protocol . $url;
$data = $this->getURLMetadata($testUrl);
- if(isset($data['title'])) {
+ if (isset($data['title'])) {
break;
}
}
@@ -527,24 +526,18 @@ class Bookmarks {
\OC::$server->getLogger()->logException($e, ['app' => 'bookmarks']);
}
if (isset($data['url'])) {
- $url = $data['url'];
+ $url = $data['url'];
}
if ((!isset($title) || trim($title) === '')) {
- $title = isset($data['title'])? $data['title'] : $url;
+ $title = isset($data['title'])? $data['title'] : $url;
}
if (isset($data['description']) && (!isset($description) || trim($description) === '')) {
- $description = $data['description'];
- }
- if (isset($data['image']) && !isset($image)) {
- $image = $data['image'];
- }
- if (isset($data['favicon']) && !isset($favicon)) {
- $favicon = $data['favicon'];
+ $description = $data['description'];
}
// Check if it is a valid URL (after adding http(s) prefix)
$urlData = parse_url($url);
- if(!$this->isProperURL($urlData)) {
+ if (!$this->isProperURL($urlData)) {
throw new \InvalidArgumentException('Invalid URL supplied');
}
@@ -579,22 +572,14 @@ class Bookmarks {
$description = $row['description'];
}
- if (!isset($image)) { // Do we replace the old description
- $image = $row['image'];
- }
-
- if (!isset($favicon)) { // Do we replace the old description
- $favicon = $row['favicon'];
- }
-
- $this->editBookmark($userid, $row['id'], $url, $title, $tags, $description, $isPublic, $image);
+ $this->editBookmark($userid, $row['id'], $url, $title, $tags, $description, $isPublic);
return $row['id'];
} else {
$qb = $this->db->getQueryBuilder();
$qb
->insert('bookmarks')
- ->values(array(
+ ->values([
'url' => $qb->createParameter('url'),
'title' => $qb->createParameter('title'),
'user_id' => $qb->createParameter('user_id'),
@@ -602,19 +587,15 @@ class Bookmarks {
'added' => $qb->createFunction('UNIX_TIMESTAMP()'),
'lastmodified' => $qb->createFunction('UNIX_TIMESTAMP()'),
'description' => $qb->createParameter('description'),
- 'image' => $qb->createParameter('image'),
- 'favicon' => $qb->createParameter('favicon'),
- ))
+ ])
->where($qb->expr()->eq('user_id', $qb->createParameter('user_id')));
- $qb->setParameters(array(
+ $qb->setParameters([
'user_id' => $userid,
'url' => $decodedUrl,
'title' => htmlspecialchars_decode($title), // XXX: Should the title update above also decode it first?
'public' => $public,
- 'description' => $description,
- 'image' => $image,
- 'favicon' => $favicon
- ));
+ 'description' => $description
+ ]);
$qb->execute();
@@ -656,15 +637,17 @@ class Bookmarks {
->where($qb->expr()->eq('bookmark_id', $qb->createNamedParameter($bookmarkID)))
->andWhere($qb->expr()->eq('tag', $qb->createNamedParameter($tag)));
- if ($qb->execute()->fetch()) continue;
+ if ($qb->execute()->fetch()) {
+ continue;
+ }
$qb = $this->db->getQueryBuilder();
$qb
->insert('bookmarks_tags')
- ->values(array(
+ ->values([
'tag' => $qb->createNamedParameter($tag),
'bookmark_id' => $qb->createNamedParameter($bookmarkID)
- ));
+ ]);
$qb->execute();
}
}
@@ -689,8 +672,9 @@ class Bookmarks {
$title = $link->nodeValue;
$ref = $link->getAttribute("href");
$tagStr = '';
- if ($link->hasAttribute("tags"))
+ if ($link->hasAttribute("tags")) {
$tagStr = $link->getAttribute("tags");
+ }
$tags = explode(',', $tagStr);
$descriptionStr = '';
@@ -724,8 +708,8 @@ class Bookmarks {
* @throws \Exception|ClientException
*/
public function getURLMetadata($url) {
- return $this->linkExplorer->get($url);
- }
+ return $this->linkExplorer->get($url);
+ }
/**
* @brief Separate Url String at comma character
@@ -734,10 +718,11 @@ class Bookmarks {
* */
public function analyzeTagRequest($line) {
$tags = explode(',', $line);
- $filterTag = array();
+ $filterTag = [];
foreach ($tags as $tag) {
- if (trim($tag) != '')
+ if (trim($tag) != '') {
$filterTag[] = trim($tag);
+ }
}
return $filterTag;
}
diff --git a/controller/lib/linkexplorer.php b/controller/lib/linkexplorer.php
index bc99318e..fadbd6dc 100644
--- a/controller/lib/linkexplorer.php
+++ b/controller/lib/linkexplorer.php
@@ -7,7 +7,7 @@ use phpUri;
class LinkExplorer {
- /**
+ /**
* @brief Load Url and receive Metadata (Title)
* @param string $url Url to load and analyze
* @return array Metadata for url;
@@ -17,13 +17,16 @@ class LinkExplorer {
// Use LinkPreview to get the meta data
$previewClient = new LinkPreview($url);
- $previewClient->getParser('general')->setMinimumImageDimension(0,0);
+ $previewClient->getParser('general')->setMinimumImageDimension(200, 200);
try {
libxml_use_internal_errors(false);
- $preview = $previewClient->getPreview('general');
+ $preview = $previewClient->getPreview('general');
} catch (\Marcelklehr\LinkPreview\Exceptions\ConnectionErrorException $e) {
\OCP\Util::writeLog('bookmarks', $e, \OCP\Util::WARN);
return $data;
+ } catch (\GuzzleHttp\Exception\ClientException $e) {
+ \OCP\Util::writeLog('bookmarks', $e, \OCP\Util::WARN);
+ return $data;
}
$data = $preview->toArray();
@@ -37,5 +40,4 @@ class LinkExplorer {
return $data;
}
-
}
diff --git a/controller/lib/imageservice.php b/controller/lib/previews/defaultpreviewservice.php
index 98b30990..fefd55ad 100644
--- a/controller/lib/imageservice.php
+++ b/controller/lib/previews/defaultpreviewservice.php
@@ -17,27 +17,31 @@
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
-
-namespace OCA\Bookmarks\Controller\Lib;
+namespace OCA\Bookmarks\Controller\Lib\Previews;
use OCP\ICache;
use OCP\ICacheFactory;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ClientException;
+use OCA\Bookmarks\Controller\Lib\LinkExplorer;
-class ImageService {
-
+class DefaultPreviewService implements IPreviewService {
// Cache for one month
const CACHE_TTL = 4 * 7 * 24 * 60 * 60;
/** @var ICache */
- private $cache;
+ protected $cache;
+
+ /** @var LinkExplorer */
+ protected $linkExplorer;
/**
* @param ICacheFactory $cacheFactory
+ * @param LinkExplorer $linkExplorer
*/
- public function __construct(ICacheFactory $cacheFactory) {
- $this->cache = $cacheFactory->create('bookmarks.images');
+ public function __construct(ICacheFactory $cacheFactory, LinkExplorer $linkExplorer) {
+ $this->cache = $cacheFactory->create('bookmarks.DefaultPreviewService');
+ $this->linkExplorer = $linkExplorer;
}
private function buildKey($url) {
@@ -48,13 +52,41 @@ class ImageService {
* @param string $url
* @return string|null image data
*/
- public function getImage($url) {
- $key = $this->buildKey($url);
+ public function getImage($bookmark) {
+ if (!isset($bookmark)) {
+ return null;
+ }
+ $site = $this->scrapeUrl($bookmark['url']);
+ if (!isset($site['image'])) {
+ return null;
+ }
+ return $this->getOrFetchImageUrl($site['image']);
+ }
+
+ public function scrapeUrl($url) {
+ $key = $this->buildKey('meta:'.$url);
+ if ($data = $this->cache->get($key)) {
+ return json_decode($data, true);
+ }
+ $data = $this->linkExplorer->get($url);
+ $this->cache->set($key, json_encode($data), self::CACHE_TTL);
+ return $data;
+ }
+
+ public function getOrFetchImageUrl($url) {
+ if (!isset($url) || $url === '') {
+ return null;
+ }
+
+ $key = $this->buildKey('image:'.$url);
// Try cache first
if ($image = $this->cache->get($key)) {
- $image = json_decode($image, true);
- return [
- 'contentType' => $image['contentType'],
+ $image = json_decode($image, true);
+ if (is_null($image)) {
+ return null;
+ }
+ return [
+ 'contentType' => $image['contentType'],
'data' => base64_decode($image['data'])
];
}
@@ -63,6 +95,8 @@ class ImageService {
$image = $this->fetchImage($url);
if (is_null($image)) {
+ $json = json_encode(null);
+ $this->cache->set($key, $json, self::CACHE_TTL);
return null;
}
@@ -73,14 +107,14 @@ class ImageService {
]);
$this->cache->set($key, $json, self::CACHE_TTL);
- return $image;
+ return $image;
}
-
+
/**
* @param string $url
* @return string|null fetched image data
*/
- private function fetchImage($url) {
+ private function fetchImage($url) {
$body = $contentType = '';
try {
$client = new \GuzzleHTTP\Client();
@@ -102,11 +136,10 @@ class ImageService {
if (!$contentType || stripos($contentType, 'image') !== 0) {
return null;
}
-
+
return [
'contentType' => $contentType,
'data' => $body
];
}
}
-
diff --git a/controller/lib/faviconservice.php b/controller/lib/previews/faviconpreviewservice.php
index 2e709514..ecb89537 100644
--- a/controller/lib/faviconservice.php
+++ b/controller/lib/previews/faviconpreviewservice.php
@@ -18,19 +18,43 @@
*
*/
-namespace OCA\Bookmarks\Controller\Lib;
+namespace OCA\Bookmarks\Controller\Lib\Previews;
use OCP\ICache;
use OCP\ICacheFactory;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ClientException;
+use OCA\Bookmarks\Controller\Lib\LinkExplorer;
+
+class FaviconPreviewService extends DefaultPreviewService {
-class FaviconService extends ImageService {
/**
* @param ICacheFactory $cacheFactory
+ * @param LinkExplorer $linkExplorer
*/
- public function __construct(ICacheFactory $cacheFactory) {
- parent::__construct($cacheFactory);
- $this->cache = $cacheFactory->create('bookmarks.favicons');
+ public function __construct(ICacheFactory $cacheFactory, LinkExplorer $linkExplorer) {
+ parent::__construct($cacheFactory, $linkExplorer);
+ $this->cache = $cacheFactory->create('bookmarks.FaviconPreviewService');
+ }
+
+ public function getImage($bookmark) {
+ if (!isset($bookmark)) {
+ return null;
+ }
+ $url = $bookmark['url'];
+ $site = $this->scrapeUrl($url);
+
+ if (isset($site['favicon'])) {
+ $image = $this->getOrFetchImageUrl($site['favicon']);
+ if (!is_null($image)) {
+ return $image;
+ }
+ }
+
+ $url_parts = parse_url($bookmark['url']);
+
+ return $this->getOrFetchImageUrl(
+ $url_parts['scheme'] . $url_parts['host'] . '/favicon.ico'
+ );
}
}
diff --git a/controller/lib/previews/ipreviewservice.php b/controller/lib/previews/ipreviewservice.php
new file mode 100644
index 00000000..bbcf2d4c
--- /dev/null
+++ b/controller/lib/previews/ipreviewservice.php
@@ -0,0 +1,9 @@
+<?php
+namespace OCA\Bookmarks\Controller\Lib\Previews;
+
+interface IPreviewService {
+ /**
+ * @return null|array ['contentType' => 'mimetype', 'data' => binary]
+ */
+ public function getImage($bookmark);
+}
diff --git a/controller/lib/previews/screenlypreviewservice.php b/controller/lib/previews/screenlypreviewservice.php
new file mode 100644
index 00000000..e42ea8f0
--- /dev/null
+++ b/controller/lib/previews/screenlypreviewservice.php
@@ -0,0 +1,132 @@
+<?php
+/**
+ * @author Marcel Klehr
+ * @copyright 2018 Marcel Klehr mklehr@gmx.net
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Bookmarks\Controller\Lib\Previews;
+
+use Wnx\ScreeenlyClient\Screenshot;
+use OCP\ICache;
+use OCP\IConfig;
+use OCP\ICacheFactory;
+
+class ScreenlyPreviewService implements IPreviewService {
+ // Cache for one month
+ const CACHE_TTL = 4 * 7 * 24 * 60 * 60;
+
+ private $apiKey;
+
+ /** @var IConfig */
+ private $config;
+
+ /** @var ICache */
+ private $cache;
+
+ private $width = 800;
+
+ private $height = 800;
+
+ /**
+ * @param ICacheFactory $cacheFactory
+ */
+ public function __construct(ICacheFactory $cacheFactory, IConfig $config) {
+ $this->config = $config;
+ $this->apiUrl = $config->getAppValue('bookmarks', 'previews.screenly.url', 'http://screeenly.com/api/v1/fullsize');
+ $this->apiKey = $config->getAppValue('bookmarks', 'previews.screenly.token', '');
+ $this->cache = $cacheFactory->create('bookmarks.ScreenlyPreviewService');
+ }
+
+ private function buildKey($url) {
+ return base64_encode($url);
+ }
+
+ /**
+ * @param string $url
+ * @return string|null image data
+ */
+ public function getImage($bookmark) {
+ if (!isset($bookmark)) {
+ return null;
+ }
+ if ('' === $this->apiKey) {
+ return null;
+ }
+ $url = $bookmark['url'];
+
+ $key = $this->buildKey($url);
+ // Try cache first
+ if ($image = $this->cache->get($key)) {
+ $image = json_decode($image, true);
+ if (is_null($image)) {
+ return null;
+ }
+ return [
+ 'contentType' => $image['contentType'],
+ 'data' => base64_decode($image['data'])
+ ];
+ }
+
+ // Fetch image from remote server
+ $image = $this->fetchScreenshot($url);
+
+ if (is_null($image)) {
+ $json = json_encode(null);
+ $this->cache->set($key, $json, self::CACHE_TTL);
+ return null;
+ }
+
+ // Store in cache for next time
+ $json = json_encode([
+ 'contentType' => $image['contentType'],
+ 'data' => base64_encode($image['data'])
+ ]);
+ $this->cache->set($key, $json, self::CACHE_TTL);
+
+ return $image;
+ }
+
+ public function fetchScreenshot($url) {
+ try {
+ $client = new \GuzzleHTTP\Client();
+ $request = $client->post($this->apiUrl, ['body' => [
+ 'key' => $this->apiKey,
+ 'url' => $url,
+ 'width' => $this->width,
+ 'height' => $this->height
+ ]
+ ]);
+ $body = $request->json();
+ } catch (\GuzzleHttp\Exception\RequestException $e) {
+ \OCP\Util::writeLog('bookmarks', $e, \OCP\Util::WARN);
+ return null;
+ } catch (\Exception $e) {
+ throw $e;
+ }
+
+ \OCP\Util::writeLog('bookmarks', $body, \OCP\Util::WARN);
+
+ // Some HTPP Error occured :/
+ if (200 != $request->getStatusCode()) {
+ return null;
+ }
+
+ return [
+ 'contentType' => 'image/jpeg',
+ 'data' => base64_decode($body['base64_raw'])
+ ];
+ }
+}
diff --git a/controller/rest/internalbookmarkcontroller.php b/controller/rest/internalbookmarkcontroller.php
index cbbe8eee..e09db4ae 100644
--- a/controller/rest/internalbookmarkcontroller.php
+++ b/controller/rest/internalbookmarkcontroller.php
@@ -20,21 +20,19 @@ use \OCP\AppFramework\Http\JSONResponse;
use \OCP\AppFramework\Http;
use \OC\User\Manager;
use \OCA\Bookmarks\Controller\Lib\Bookmarks;
-use \OCA\Bookmarks\Controller\Lib\ImageService;
-use \OCA\Bookmarks\Controller\Lib\FaviconService;
+use \OCA\Bookmarks\Controller\Lib\Previews\IPreviewService;
use DateInterval;
use DateTime;
use OCP\AppFramework\Utility\ITimeFactory;
class InternalBookmarkController extends ApiController {
-
const IMAGES_CACHE_TTL = 7 * 24 * 60 * 60;
private $publicController;
private $userId;
private $libBookmarks;
- private $imageService;
+ private $previewService;
private $faviconService;
private $timeFactory;
@@ -46,16 +44,18 @@ class InternalBookmarkController extends ApiController {
IL10N $l10n,
Bookmarks $bookmarks,
Manager $userManager,
- ImageService $imageService,
- FaviconService $faviconService,
+ IPreviewService $previewService,
+ IPreviewService $faviconService,
+ IPreviewService $screenshotService,
ITimeFactory $timeFactory
) {
parent::__construct($appName, $request);
$this->publicController = new BookmarkController($appName, $request, $userId, $db, $l10n, $bookmarks, $userManager);
$this->userId = $userId;
$this->libBookmarks = $bookmarks;
- $this->imageService = $imageService;
+ $this->previewService = $previewService;
$this->faviconService = $faviconService;
+ $this->screenshotService = $screenshotService;
$this->timeFactory = $timeFactory;
}
@@ -81,17 +81,17 @@ class InternalBookmarkController extends ApiController {
$page = 0,
$sort = "bookmarks_sorting_recent", // legacy
$user = null,
- $tags = array(),
+ $tags = [],
$conjunction = "or",
$sortby = "",
- $search = array(),
+ $search = [],
$limit = 10,
$untagged = false
) {
return $this->publicController->getBookmarks($type, $tag, $page, $sort, $user, $tags, $conjunction, $sortby, $search, $limit, $untagged);
}
- /**
+ /**
* @param string $id
* @param string $user
* @return JSONResponse
@@ -112,7 +112,7 @@ class InternalBookmarkController extends ApiController {
*
* @NoAdminRequired
*/
- public function newBookmark($url = "", $item = array(), $title = "", $is_public = false, $description = "") {
+ public function newBookmark($url = "", $item = [], $title = "", $is_public = false, $description = "") {
return $this->publicController->newBookmark($url, $item, $title, $is_public, $description);
}
@@ -128,7 +128,7 @@ class InternalBookmarkController extends ApiController {
*
* @NoAdminRequired
*/
- public function legacyEditBookmark($id = null, $url = "", $item = array(), $title = "", $is_public = false, $record_id = null, $description = "") {
+ public function legacyEditBookmark($id = null, $url = "", $item = [], $title = "", $is_public = false, $record_id = null, $description = "") {
return $this->publicController->legacyEditBookmark($id, $url, $item, $title, $is_public, $record_id, $description);
}
@@ -145,7 +145,7 @@ class InternalBookmarkController extends ApiController {
*
* @NoAdminRequired
*/
- public function editBookmark($id = null, $url = "", $item = array(), $title = "", $is_public = false, $record_id = null, $description = "", $tags = []) {
+ public function editBookmark($id = null, $url = "", $item = [], $title = "", $is_public = false, $record_id = null, $description = "", $tags = []) {
return $this->publicController->editBookmark($id, $url, $item, $title, $is_public, $record_id, $description, $tags);
}
@@ -207,16 +207,17 @@ class InternalBookmarkController extends ApiController {
*/
public function getBookmarkImage($id) {
$bookmark = $this->libBookmarks->findUniqueBookmark($id, $this->userId);
- if (!isset($bookmark) || !isset($bookmark['image']) || $bookmark['image'] === '') {
- return new NotFoundResponse();
+ $image = $this->previewService->getImage($bookmark);
+ if (isset($image)) {
+ return $this->doImageResponse($image);
}
- $image = $this->imageService->getImage($bookmark['image']);
- if (!isset($image)) {
- return new NotFoundResponse();
+ $image = $this->screenshotService->getImage($bookmark);
+ if (isset($image)) {
+ return $this->doImageResponse($image);
}
-
- return $this->doImageResponse($image);
+
+ return new NotFoundResponse();
}
/**
@@ -229,11 +230,7 @@ class InternalBookmarkController extends ApiController {
*/
public function getBookmarkFavicon($id) {
$bookmark = $this->libBookmarks->findUniqueBookmark($id, $this->userId);
- if (!isset($bookmark) || !isset($bookmark['favicon']) || $bookmark['favicon'] === '') {
- return new NotFoundResponse();
- }
-
- $image = $this->faviconService->getImage($bookmark['favicon']);
+ $image = $this->faviconService->getImage($bookmark);
if (!isset($image)) {
return new NotFoundResponse();
}
diff --git a/css/bookmarks.css b/css/bookmarks.css
index 0248baf2..96046f53 100644
--- a/css/bookmarks.css
+++ b/css/bookmarks.css
@@ -191,11 +191,14 @@ button span,
background-color: rgb(210, 210, 210);
background-size: cover;
background-position: center;
+ box-shadow: inset 0 0 150px rgba(0, 0, 0, 0.65);
}
.bookmark-card:hover {
box-shadow: 0 1px 10px rgba(77, 77, 77, 0.75);
+ box-shadow: inset 0 0 150px rgba(0, 0, 0, 0.65);
opacity: 1;
+ cursor: pointer;
}
.selection-active .bookmark-card {
@@ -229,27 +232,30 @@ button span,
bottom: 0;
left: 0;
right: 0;
- padding: 0.5cm 0.5cm 0.2cm;
+ padding: 1cm 0.5cm 0.2cm 0.5cm;
background: -moz-linear-gradient(
top,
rgba(0, 0, 0, 0) 0%,
+ rgba(0, 0, 0, 0.4) 40%,
rgba(0, 0, 0, 0.65) 100%
);
background: -webkit-linear-gradient(
top,
rgba(0, 0, 0, 0) 0%,
+ rgba(0, 0, 0, 0.4) 40%,
rgba(0, 0, 0, 0.65) 100%
);
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
+ rgba(0, 0, 0, 0.4) 40%,
rgba(0, 0, 0, 0.65) 100%
);
}
.bookmark-card .popovermenu {
position: absolute;
- top: 1.5cm;
+ top: 2cm;
right: 0.1cm;
margin-right: -4px;
}
@@ -258,7 +264,7 @@ button span,
background-color: transparent;
border: none;
position: absolute;
- top: 0.6cm;
+ top: 1.1cm;
right: 0.1cm;
}
@@ -283,6 +289,10 @@ button span,
cursor: pointer;
}
+.bookmark-card h2:hover {
+ text-decoration: underline;
+}
+
.bookmark-card h2.with-favicon {
/*background-image: bookmark/{id}/favicon*/
background-position: 0px 5px;
@@ -339,6 +349,16 @@ button span,
}
}
+.bookmark-detail .preview {
+ height: 7cm;
+ background-color: rgb(210, 210, 210);
+ background-size: cover;
+ background-position: center;
+ box-shadow: inset 0 0 150px rgba(0, 0, 0, 0.65);
+ margin: -1cm;
+ margin-bottom: 0.25cm;
+}
+
.bookmark-detail h1 {
font-size: 0.7cm;
line-height: 1.3;
diff --git a/img/bookmarks-black.svg b/img/bookmarks-black.svg
new file mode 100644
index 00000000..b843bf71
--- /dev/null
+++ b/img/bookmarks-black.svg
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ height="32"
+ width="32"
+ viewBox="0 0 32 32"
+ version="1.1"
+ id="svg10"
+ sodipodi:docname="bookmarks-black.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata16">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs14" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="792"
+ inkscape:window-height="480"
+ id="namedview12"
+ showgrid="false"
+ inkscape:zoom="7.375"
+ inkscape:cx="16"
+ inkscape:cy="16"
+ inkscape:window-x="477"
+ inkscape:window-y="184"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g8" />
+ <g
+ transform="translate(0,16)"
+ fill="#fff"
+ id="g8">
+ <path
+ d="m16-14c0.94487 0 3.9911 7.9919 4.7555 8.5752 0.76441 0.5833 8.9427 1.1565 9.2346 2.1003 0.29198 0.9438-6.0036 6.4562-6.2956 7.4-0.29198 0.9438 2.3984 9.2899 1.6339 9.8732-0.764 0.583-8.383-4.002-9.328-4.002-0.94487 0-8.5641 4.585-9.3285 4.0017-0.7644-0.584 1.9259-8.9297 1.6339-9.8735s-6.5875-6.4562-6.2956-7.4c0.292-0.9438 8.4702-1.517 9.2342-2.1003 0.765-0.5833 3.811-8.5752 4.756-8.5752z"
+ id="path2"
+ style="fill:#000000" />
+ <path
+ opacity=".3"
+ d="m88-14c0.94487 0 3.9911 7.9919 4.7555 8.5752 0.76441 0.5833 8.9427 1.1565 9.2346 2.1003 0.29198 0.9438-6.0036 6.4562-6.2956 7.4-0.29198 0.9438 2.3984 9.2899 1.6339 9.8732-0.764 0.583-8.383-4.002-9.328-4.002-0.94487 0-8.5641 4.585-9.3285 4.0017-0.76441-0.5833 1.9259-8.9294 1.6339-9.8732-0.29198-0.9438-6.5875-6.4562-6.2956-7.4 0.29198-0.9438 8.4702-1.517 9.2346-2.1003 0.76441-0.5833 3.8106-8.5752 4.7555-8.5752z"
+ id="path4" />
+ <path
+ opacity=".7"
+ d="m34.344 13.406c-0.172 0.088-0.315 0.187-0.344 0.282-0.28187 0.91113 5.5814 6.0441 6.25 7.25 0.06311-0.4005 0.10474-0.73846 0.0625-0.875-0.24735-0.79953-4.7593-4.8544-5.9688-6.6562zm27.312 0c-1.2095 1.8019-5.7214 5.8567-5.9688 6.6562-0.04224 0.13654-0.00061 0.4745 0.0625 0.875 0.66855-1.2059 6.5319-6.3389 6.25-7.25-0.0292-0.09438-0.17213-0.1939-0.34375-0.28125zm-13.656 12.532c-0.94487 0-8.5793 4.5833-9.3438 4-0.03185-0.0243-0.04218-0.07484-0.0625-0.125-0.06113 0.57179-0.08345 1.0136 0.0625 1.125 0.76442 0.5833 8.3989-4 9.3438-4 0.94487 0 8.5793 4.5833 9.3438 4 0.14595-0.11137 0.12363-0.55321 0.0625-1.125-0.02032 0.05016-0.03065 0.1007-0.0625 0.125-0.76441 0.5833-8.3989-4-9.3438-4z"
+ transform="translate(0,-16)"
+ id="path6" />
+ </g>
+</svg>
diff --git a/js/admin.js b/js/admin.js
new file mode 100644
index 00000000..5863f518
--- /dev/null
+++ b/js/admin.js
@@ -0,0 +1,36 @@
+(function(window, OCP, $) {
+ [
+ {
+ el: '#bookmarks_previews_screenly_token',
+ setting: 'previews.screenly.token'
+ },
+ {
+ el: '#bookmarks_previews_screenly_url',
+ setting: 'previews.screenly.url'
+ }
+ ].forEach(function(entry) {
+ $el = $(entry.el);
+ $statusSuccess = $(entry.el + ' ~ .success-status');
+ $statusError = $(entry.el + ' ~ .error-status');
+
+ $statusSuccess.hide();
+ $statusError.hide();
+
+ $el.on('change', function() {
+ OCP.AppConfig.setValue('bookmarks', entry.setting, $el.val(), {
+ success: function() {
+ $statusSuccess.show();
+ setTimeout(function() {
+ $statusSuccess.fadeOut();
+ }, 3000);
+ },
+ error: function() {
+ $statusError.show();
+ setTimeout(function() {
+ $statusError.fadeOut();
+ }, 3000);
+ }
+ });
+ });
+ });
+})(window, OCP, $);
diff --git a/js/models/Bookmark.js b/js/models/Bookmark.js
index c579e554..cd4eba07 100644
--- a/js/models/Bookmark.js
+++ b/js/models/Bookmark.js
@@ -1,16 +1,30 @@
import Backbone from 'backbone';
-import $ from 'jquery'
+import colorPalettes from 'nice-color-palettes';
+import $ from 'jquery';
+
+// 100 palettes * 5 colors = 500 colors
+const COLORS = colorPalettes.reduce((p1, p2) => p1.concat(p2), []);
+const simpleHash = str => {
+ var hash = 0;
+ for (var i = 0; i < str.length; i++) {
+ hash = str.charCodeAt(i) + (hash << 6) + (hash << 16) - hash;
+ }
+ return Math.abs(hash);
+};
export default Backbone.Model.extend({
urlRoot: 'bookmark',
clickLink: function() {
- const url = encodeURIComponent(this.get('url'))
+ const url = encodeURIComponent(this.get('url'));
$.ajax({
method: 'POST',
- url: 'bookmark/click?url='+url,
+ url: 'bookmark/click?url=' + url,
headers: {
- 'requesttoken': oc_requesttoken
+ requesttoken: oc_requesttoken
}
- })
+ });
},
+ getColor: function() {
+ return COLORS[simpleHash(new URL(this.get('url')).host) % COLORS.length];
+ }
});
diff --git a/js/templates/BookmarkCard.html b/js/templates/BookmarkCard.html
index 8c33a283..3d6e8e67 100644
--- a/js/templates/BookmarkCard.html
+++ b/js/templates/BookmarkCard.html
@@ -1,6 +1,6 @@
<input type="checkbox" name="select" class="checkbox select-mode-checkbox" /><label for="select"></label>
<div class="panel">
- <h2 <%= favicon ? "style=\"background-image: url('bookmark/" + id + "/favicon');\"" : '' %> class="<%- favicon? 'with-favicon' : '' %>">
+ <h2 style="background-image: url('bookmark/<%- id %>/favicon');" class="with-favicon">
<a target="_blank" href="<%- url %>"><%- title %></a>
</h2>
<button class="toggle-actions icon-more-white"></button>
diff --git a/js/templates/BookmarkDetail.html b/js/templates/BookmarkDetail.html
index da7dd65a..c3db429d 100644
--- a/js/templates/BookmarkDetail.html
+++ b/js/templates/BookmarkDetail.html
@@ -3,6 +3,7 @@
<span class="message message-saving"><span class="icon-loading"></span></span>
<span class="message message-saved"><span class="icon-checkmark"></span> <%- t('bookmarks', 'Saved') %></span>
</div>
+<div class="preview"></div>
<h1 data-attribute="title"><%- title %></h1>
<h3>Link</h3>
<h2 data-attribute="url">
diff --git a/js/views/BookmarkCard.js b/js/views/BookmarkCard.js
index 5848b1e2..f149cfcf 100644
--- a/js/views/BookmarkCard.js
+++ b/js/views/BookmarkCard.js
@@ -3,41 +3,31 @@ import Backbone from 'backbone';
import Tags from '../models/Tags';
import TagsNavigationView from './TagsNavigation';
import templateString from '../templates/BookmarkCard.html';
-import colorPalettes from 'nice-color-palettes';
const Marionette = Backbone.Marionette;
const Radio = Backbone.Radio;
-// 100 palettes * 5 colors = 500 colors
-const COLORS = colorPalettes.reduce((p1, p2) => p1.concat(p2), []);
-const simpleHash = (str) => {
- var hash = 0;
- for (var i = 0; i<str.length; i++){
- hash = str.charCodeAt(i) + (hash << 6) + (hash << 16) - hash;
- }
- return Math.abs(hash);
-};
-
export default Marionette.View.extend({
template: _.template(templateString),
className: 'bookmark-card',
ui: {
- 'link': 'h2 > a',
- 'checkbox': '.selectbox',
- 'actionsMenu': '.popovermenu',
- 'actionsToggle': '.toggle-actions'
+ link: 'h2 > a',
+ checkbox: '.selectbox',
+ actionsMenu: '.popovermenu',
+ actionsToggle: '.toggle-actions'
},
regions: {
- 'tags': '.tags'
+ tags: '.tags'
},
events: {
- 'click': 'clickLink',
+ click: 'open',
+ 'click @ui.link': 'clickLink',
'click @ui.checkbox': 'select',
'click @ui.actionsToggle': 'toggleActions',
'click .menu-filter-add': 'select',
'click .menu-filter-remove': 'select',
'click .menu-delete': 'delete',
- 'click .menu-details': 'open',
+ 'click .menu-details': 'open'
},
initialize: function(opts) {
this.app = opts.app;
@@ -49,19 +39,33 @@ export default Marionette.View.extend({
},
onRender: function() {
var that = this;
- if (this.model.get('image')) {
- this.$el.css('background-image', 'url(bookmark/'+this.model.get('id')+'/image)');
- } else {
- this.$el.css('background-color', COLORS[simpleHash(new URL(this.model.get('url')).host) % COLORS.length]);
- }
- var tags = new Tags(this.model.get('tags').map(function(id) {
- return that.app.tags.findWhere({name: id});
- }));
- this.showChildView('tags', new TagsNavigationView({collection: tags}));
+ this.$el.css(
+ 'background-image',
+ 'url(bookmark/' + this.model.get('id') + '/image)'
+ );
+ this.$el.css('background-color', this.model.getColor());
+
+ var tags = new Tags(
+ this.model.get('tags').map(function(id) {
+ return that.app.tags.findWhere({ name: id });
+ })
+ );
+ this.showChildView('tags', new TagsNavigationView({ collection: tags }));
this.$('.checkbox').prop('checked', this.$el.hasClass('active'));
},
clickLink: function(e) {
- if (e && e.target === this.getUI('actionsToggle')[0]) {
+ if (e && e.target === this.getUI('actionsToggle')[0]) {
+ return;
+ }
+ this.model.clickLink();
+ },
+ open: function(e) {
+ if (
+ e &&
+ (this.getUI('actionsToggle')[0] === e.target ||
+ this.getUI('link')[0] === e.target ||
+ $.contains(this.$('.tags')[0], e.target))
+ ) {
return;
}
if (this.$el.closest('.selection-active').length) {
@@ -69,25 +73,26 @@ export default Marionette.View.extend({
e.preventDefault();
return;
}
- this.model.clickLink();
- },
- open: function() {
Radio.channel('details').trigger('show', this.model);
},
toggleActions: function() {
- this.getUI('actionsMenu').toggleClass('open').toggleClass('closed');
+ this.getUI('actionsMenu')
+ .toggleClass('open')
+ .toggleClass('closed');
},
closeActions: function(e) {
if (e && this.getUI('actionsToggle')[0] === e.target) {
return;
}
- this.getUI('actionsMenu').removeClass('open').addClass('closed');
+ this.getUI('actionsMenu')
+ .removeClass('open')
+ .addClass('closed');
},
select: function(e) {
e.stopPropagation();
if (this.$el.hasClass('active')) {
this.model.trigger('unselect', this.model);
- }else{
+ } else {
this.model.trigger('select', this.model);
}
},
diff --git a/js/views/BookmarkDetail.js b/js/views/BookmarkDetail.js
index f9126a4b..56b1c9c2 100644
--- a/js/views/BookmarkDetail.js
+++ b/js/views/BookmarkDetail.js
@@ -18,6 +18,7 @@ export default Marionette.View.extend({
}
},
ui: {
+ preview: '.preview',
link: 'h2 > a',
close: '> .close',
edit: '.edit',
@@ -47,6 +48,12 @@ export default Marionette.View.extend({
this.listenTo(this.tags, 'add remove', this.submitTags);
},
onRender: function() {
+ this.getUI('preview').css(
+ 'background-image',
+ 'url(bookmark/' + this.model.get('id') + '/image)'
+ );
+ this.getUI('preview').css('background-color', this.model.getColor());
+
this.showChildView(
'tags',
new TagsSelectionView({
diff --git a/settings/adminsection.php b/settings/adminsection.php
new file mode 100644
index 00000000..d197b11a
--- /dev/null
+++ b/settings/adminsection.php
@@ -0,0 +1,47 @@
+<?php
+namespace OCA\Bookmarks\Settings;
+
+use OCP\IL10N;
+use OCP\Settings\IIconSection;
+
+class AdminSection implements IIconSection {
+
+ /** @var IL10N */
+ private $l;
+
+ public function __construct(IL10N $l) {
+ $this->l = $l;
+ }
+
+ /**
+ * returns the ID of the section. It is supposed to be a lower case string
+ *
+ * @returns string
+ */
+ public function getID() {
+ return 'bookmarks';
+ }
+
+ /**
+ * returns the translated name as it should be displayed, e.g. 'LDAP / AD
+ * integration'. Use the L10N service to translate it.
+ *
+ * @return string
+ */
+ public function getName() {
+ return $this->l->t('Bookmarks');
+ }
+
+ public function getIcon() {
+ return '/custom_apps/bookmarks/img/bookmarks-black.svg';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the settings navigation. The sections are arranged in ascending order of
+ * the priority values. It is required to return a value between 0 and 99.
+ */
+ public function getPriority() {
+ return 80;
+ }
+}
diff --git a/settings/adminsettings.php b/settings/adminsettings.php
new file mode 100644
index 00000000..46d89b07
--- /dev/null
+++ b/settings/adminsettings.php
@@ -0,0 +1,57 @@
+<?php
+namespace OCA\Bookmarks\Settings;
+
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\Settings\ISettings;
+
+class AdminSettings implements ISettings {
+ /** @var IConfig */
+ private $config;
+
+ /** @var IL10N */
+ private $l;
+
+ /**
+ * Admin constructor.
+ *
+ * @param IConfig $config
+ * @param IL10N $l
+ */
+ public function __construct(
+ IConfig $config,
+ IL10N $l
+ ) {
+ $this->config = $config;
+ $this->l = $l;
+ }
+
+ /**
+ * @return TemplateResponse
+ */
+ public function getForm() {
+ $parameters = [
+ 'previews.screenly.url' => $this->config->getAppValue('bookmarks', 'previews.screenly.url', 'http://screeenly.com/api/v1/fullsize'),
+ 'previews.screenly.token' => $this->config->getAppValue('bookmarks', 'previews.screenly.token', '')
+ ];
+
+ return new TemplateResponse('bookmarks', 'admin', $parameters);
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ */
+ public function getSection() {
+ return 'bookmarks';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ */
+ public function getPriority() {
+ return 50;
+ }
+}
diff --git a/templates/admin.php b/templates/admin.php
new file mode 100644
index 00000000..40fe9302
--- /dev/null
+++ b/templates/admin.php
@@ -0,0 +1,30 @@
+<?php
+/** @var $l \OCP\IL10N */
+/** @var $_ array */
+script('bookmarks', 'admin');
+?>
+
+<div id="bookmarks" class="section">
+ <h2><?php p($l->t('Previews')); ?></h2>
+ <p>
+ <?php p($l->t('In order to display real screenshots of your bookmarked websites, Bookmarks can use a third-party service to generate those.')); ?>
+ </p>
+ <p>
+ <?php print($l->t('You can either sign up for free at <a href="https://screeenly.com">screeenly.com</a> or <a href="https://github.com/stefanzweifel/screeenly">setup your own server</a>.')); ?>
+ </p>
+ <p>
+ <label for="bookmarks_previews_screenly_url"><?php p($l->t('Screenly API URL')); ?></label>
+ <input id="bookmarks_previews_screenly_url" name="bookmarks_previews_screenly_url"
+ type="text" style="width: 250px;" value="<?php p($_['previews.screenly.url']); ?>" />
+ <span class="error-status icon-error-color" style="display: inline-block"></span>
+ <span class="success-status icon-checkmark-color" style="display: inline-block"></span>
+
+ </p>
+ <p>
+ <label for="bookmarks_previews_screenly_token"><?php p($l->t('Screenly API key')); ?></label>
+ <input id="bookmarks_previews_screenly_token" name="bookmarks_previews_screenly_token"
+ type="text" style="width: 250px;" value="<?php p($_['previews.screenly.token']); ?>" />
+ <span class="error-status icon-error-color" style="display: inline-block"></span>
+ <span class="success-status icon-checkmark-color" style="display: inline-block"></span>
+ </p>
+</div>