1
0
mirror of https://github.com/Kovah/LinkAce.git synced 2025-04-14 03:32:01 +02:00

Merge pull request #261 from Kovah/dev

Release
This commit is contained in:
Kevin Woblick 2021-04-18 14:34:00 +02:00 committed by GitHub
commit 85461ae4b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 508 additions and 313 deletions

View File

@ -39,7 +39,7 @@ class ImportHtmlBookmarks
}
if ($generateMeta) {
$linkMeta = HtmlMeta::getFromUrl($link['uri']);
$linkMeta = (new HtmlMeta)->getFromUrl($link['uri']);
$title = $link['title'] ?: $linkMeta['title'];
$description = $link['note'] ?: $linkMeta['description'];
} else {

View File

@ -8,7 +8,7 @@ use Venturecraft\Revisionable\Revision;
class CleanupLinkHistoriesCommand extends Command
{
protected $signature = 'link:cleanup-histories {field?}';
protected $signature = 'links:cleanup-histories {field?}';
protected $description = 'Removes all but the last 5 entries in the link histories.
{field : If provided, only history entries of that field are deleted}';

View File

@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use App\Helper\HtmlMeta;
use App\Models\Link;
use Illuminate\Console\Command;
class UpdateLinkThumbnails extends Command
{
protected $signature = 'links:update-thumbnails';
protected $description = 'Updates the thumbnails for all existing links, done in batches.';
public function handle()
{
$this->confirm('This command updates the thumbnail for all links with the status "ok". This can take a long time, depending on the amount of links you have saved. Do you want to proceed?');
$totalCount = Link::where('status', Link::STATUS_OK)->count();
$processedLinks = 0;
if ($totalCount === 0) {
$this->warn('No links with status "ok" found. Aborting');
}
$this->comment("Started processing of $totalCount links...");
Link::where('status', Link::STATUS_OK)->latest()
->chunk(100, function ($links) use ($processedLinks, $totalCount) {
foreach ($links as $link) {
$this->updateThumbnailForLink($link);
sleep(1); // Rate limiting of outgoing traffic
}
$processedLinks += count($links);
$this->comment("Processed $processedLinks of $totalCount links.");
});
$this->info('Finished processing all links.');
}
protected function updateThumbnailForLink(Link $link): void
{
$meta = (new HtmlMeta)->getFromUrl($link->url);
if ($meta['thumbnail'] !== null) {
$link->thumbnail = $meta['thumbnail'];
$link->save();
}
}
}

View File

@ -7,6 +7,7 @@ use App\Console\Commands\CleanupLinkHistoriesCommand;
use App\Console\Commands\RegisterUserCommand;
use App\Console\Commands\ResetPasswordCommand;
use App\Console\Commands\ImportCommand;
use App\Console\Commands\UpdateLinkThumbnails;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -28,6 +29,7 @@ class Kernel extends ConsoleKernel
ResetPasswordCommand::class,
CleanupLinkHistoriesCommand::class,
ImportCommand::class,
UpdateLinkThumbnails::class,
];
/**

View File

@ -8,8 +8,14 @@ use Kovah\HtmlMeta\Exceptions\UnreachableUrlException;
class HtmlMeta
{
/** @var string */
protected $url;
/** @var array */
protected static $fallback;
protected $fallback;
/** @var array */
protected $meta;
/**
* Get the title and description of an URL.
@ -25,60 +31,99 @@ class HtmlMeta
* @param bool $flashAlerts
* @return array
*/
public static function getFromUrl(string $url, bool $flashAlerts = false): array
public function getFromUrl(string $url, bool $flashAlerts = false): array
{
self::buildFallback($url);
$this->url = $url;
$this->buildFallback();
try {
$meta = \Kovah\HtmlMeta\Facades\HtmlMeta::forUrl($url);
$this->meta = \Kovah\HtmlMeta\Facades\HtmlMeta::forUrl($url);
} catch (InvalidUrlException $e) {
Log::warning($url . ': ' . $e->getMessage());
if ($flashAlerts) {
flash(trans('link.added_connection_error'), 'warning');
}
return self::$fallback;
return $this->fallback;
} catch (UnreachableUrlException $e) {
Log::warning($url . ': ' . $e->getMessage());
if ($flashAlerts) {
flash(trans('link.added_request_error'), 'warning');
}
return self::$fallback;
return $this->fallback;
}
return self::buildLinkMeta($meta);
return $this->buildLinkMeta();
}
/**
* Build a response array containing the link meta including a success flag.
*
* @param array $metaTags
* @return array
*/
protected static function buildLinkMeta(array $metaTags): array
protected function buildLinkMeta(): array
{
$metaTags['description'] = $metaTags['description']
?? $metaTags['og:description']
?? $metaTags['twitter:description']
$this->meta['description'] = $this->meta['description']
?? $this->meta['og:description']
?? $this->meta['twitter:description']
?? null;
return [
'success' => true,
'title' => $metaTags['title'] ?? self::$fallback['title'],
'description' => $metaTags['description'],
'title' => $this->meta['title'] ?? $this->fallback['title'],
'description' => $this->meta['description'],
'thumbnail' => $this->getThumbnail(),
];
}
/**
* The fallback is used in case of errors while trying to get the link meta.
*
* @param string $url
*/
protected static function buildFallback(string $url): void
protected function buildFallback(): void
{
self::$fallback = [
$this->fallback = [
'success' => false,
'title' => parse_url($url, PHP_URL_HOST) ?? $url,
'title' => parse_url($this->url, PHP_URL_HOST) ?? $this->url,
'description' => false,
'thumbnail' => null,
];
}
/**
* Try to get the thumbnail from the meta tags and handle specific cases where we know how to get a proper image
* from the website.
*
* @return string|null
*/
protected function getThumbnail(): ?string
{
$thumbnail = $this->meta['og:image']
?? $this->meta['twitter:image']
?? null;
if (!is_null($thumbnail) && parse_url($thumbnail, PHP_URL_HOST) === null) {
// If the thumbnail does not contain the domain, add it in front of it
$urlInfo = parse_url($this->url);
$baseUrl = sprintf('%s://%s/', $urlInfo['scheme'], $urlInfo['host']);
$thumbnail = $baseUrl . trim($thumbnail, '/');
}
/*
* Edge case of Youtube only (because of Youtube EU cookie consent)
* Formula based on https://stackoverflow.com/a/2068371, returns Youtube image url
* https://img.youtube.com/vi/[video-id]/mqdefault.jpg
*/
if (is_null($thumbnail)) {
if (str_contains($this->url, 'youtube.com') && str_contains($this->url, 'v=')) {
preg_match('/v=([a-zA-Z0-9]+)/', $this->url, $matched);
$thumbnail = isset($matched[1]) ? 'https://img.youtube.com/vi/' . $matched[1] . '/mqdefault.jpg' : null;
}
if (str_contains($this->url, 'youtu.be')) {
preg_match('/youtu.be\/([a-zA-Z0-9_]+)/', $this->url, $matched);
$thumbnail = isset($matched[1]) ? 'https://img.youtube.com/vi/' . $matched[1] . '/mqdefault.jpg' : null;
}
}
return $thumbnail;
}
}

View File

@ -15,7 +15,7 @@ use Illuminate\Support\Facades\Storage;
*/
class UpdateHelper
{
protected static $releaseApiUrl = 'https://api.github.com/repos/kovah/linkace/releases';
protected const RELEASE_API_URL = 'https://updates.linkace.org/api/current-version';
/**
* Get the current version from the package.json file and cache it for a day.
@ -71,14 +71,8 @@ class UpdateHelper
*/
protected static function getCurrentVersionFromAPI(): ?string
{
$response = Http::get(self::$releaseApiUrl);
$response = Http::get(self::RELEASE_API_URL);
if (!$response->successful()) {
return null;
}
$releases = $response->json();
return $releases[0]['tag_name'] ?? null;
return $response->successful() ? $response->body() : null;
}
}

View File

@ -32,7 +32,7 @@ function usersettings(string $key = '')
* Retrieve system settings
*
* @param string $key
* @return null|Collection
* @return null|Collection|string
*/
function systemsettings(string $key = '')
{

View File

@ -42,7 +42,7 @@ class BookmarkletController extends Controller
session(['bookmarklet.create' => true]);
return view('actions.bookmarklet.create', [
return view('app.bookmarklet.create', [
'bookmark_url' => $newUrl,
'bookmark_title' => $newTitle,
'bookmark_description' => $newDescription,
@ -56,7 +56,7 @@ class BookmarkletController extends Controller
*/
public function getCompleteView(): View
{
return view('actions.bookmarklet.complete');
return view('app.bookmarklet.complete');
}
/**
@ -66,6 +66,6 @@ class BookmarkletController extends Controller
*/
public function getLoginForm(): View
{
return view('actions.bookmarklet.login');
return view('app.bookmarklet.login');
}
}

View File

@ -22,7 +22,7 @@ class ExportController extends Controller
*/
public function getExport(): View
{
return view('actions.export.export');
return view('app.export.export');
}
/**
@ -39,7 +39,7 @@ class ExportController extends Controller
{
$links = Link::orderBy('title', 'asc')->with('tags')->get();
$fileContent = view()->make('actions.export.html-export', ['links' => $links])->render();
$fileContent = view()->make('app.export.html-export', ['links' => $links])->render();
$fileName = config('app.name') . '_export.html';
return response()->streamDownload(function () use ($fileContent) {

View File

@ -21,7 +21,7 @@ class FeedController extends Controller
'id' => $request->fullUrl(),
];
return new Response(view('actions.feed.links', [
return new Response(view('app.feed.links', [
'meta' => $meta,
'links' => $links,
]), 200, ['Content-Type' => 'application/xml']);
@ -37,7 +37,7 @@ class FeedController extends Controller
'id' => $request->fullUrl(),
];
return new Response(view('actions.feed.lists', [
return new Response(view('app.feed.lists', [
'meta' => $meta,
'lists' => $lists,
]), 200, ['Content-Type' => 'application/xml']);
@ -53,7 +53,7 @@ class FeedController extends Controller
'id' => $request->fullUrl(),
];
return new Response(view('actions.feed.links', [
return new Response(view('app.feed.links', [
'meta' => $meta,
'links' => $links,
]), 200, ['Content-Type' => 'application/xml']);
@ -69,7 +69,7 @@ class FeedController extends Controller
'id' => $request->fullUrl(),
];
return new Response(view('actions.feed.tags', [
return new Response(view('app.feed.tags', [
'meta' => $meta,
'tags' => $tags,
]), 200, ['Content-Type' => 'application/xml']);
@ -85,7 +85,7 @@ class FeedController extends Controller
'id' => $request->fullUrl(),
];
return new Response(view('actions.feed.links', [
return new Response(view('app.feed.links', [
'meta' => $meta,
'links' => $links,
]), 200, ['Content-Type' => 'application/xml']);

View File

@ -18,7 +18,7 @@ class ImportController extends Controller
*/
public function getImport(): View
{
return view('actions.import.import');
return view('app.import.import');
}
/**

View File

@ -18,7 +18,7 @@ class SearchController extends Controller
*/
public function getSearch(): View
{
return view('actions.search.search')
return view('app.search.search')
->with('results', collect([]))
->with('order_by_options', $this->orderByOptions)
->with('query_settings', [
@ -46,7 +46,7 @@ class SearchController extends Controller
$search = $this->buildDatabaseQuery($request);
$results = $search->paginate(getPaginationLimit());
return view('actions.search.search')
return view('app.search.search')
->with('results', $results)
->with('order_by_options', $this->orderByOptions)
->with('query_settings', [

View File

@ -22,7 +22,7 @@ class SystemSettingsController extends Controller
*/
public function getSystemSettings(): View
{
return view('actions.settings.system', [
return view('app.settings.system', [
'linkaceVersion' => UpdateHelper::currentVersion(),
]);
}

View File

@ -38,7 +38,7 @@ class TrashController extends Controller
->byUser(auth()->id())
->get();
return view('actions.trash.index', [
return view('app.trash.index', [
'links' => $links,
'lists' => $lists,
'tags' => $tags,

View File

@ -27,7 +27,7 @@ class UserSettingsController extends Controller
{
$bookmarkletCode = LinkAce::generateBookmarkletCode();
return view('actions.settings.user', [
return view('app.settings.user', [
'user' => auth()->user(),
'bookmarklet_code' => $bookmarkletCode,
]);

View File

@ -21,7 +21,7 @@ class FeedController extends Controller
'id' => $request->fullUrl(),
];
return new Response(view('actions.feed.links', [
return new Response(view('app.feed.links', [
'meta' => $meta,
'links' => $links,
]), 200, ['Content-Type' => 'application/xml']);
@ -37,7 +37,7 @@ class FeedController extends Controller
'id' => $request->fullUrl(),
];
return new Response(view('actions.feed.lists', [
return new Response(view('app.feed.lists', [
'meta' => $meta,
'lists' => $lists,
]), 200, ['Content-Type' => 'application/xml']);
@ -54,7 +54,7 @@ class FeedController extends Controller
'id' => $request->fullUrl(),
];
return new Response(view('actions.feed.links', [
return new Response(view('app.feed.links', [
'meta' => $meta,
'links' => $links,
]), 200, ['Content-Type' => 'application/xml']);
@ -70,7 +70,7 @@ class FeedController extends Controller
'id' => $request->fullUrl(),
];
return new Response(view('actions.feed.tags', [
return new Response(view('app.feed.tags', [
'meta' => $meta,
'tags' => $tags,
]), 200, ['Content-Type' => 'application/xml']);
@ -87,7 +87,7 @@ class FeedController extends Controller
'id' => $request->fullUrl(),
];
return new Response(view('actions.feed.links', [
return new Response(view('app.feed.links', [
'meta' => $meta,
'links' => $links,
]), 200, ['Content-Type' => 'application/xml']);

View File

@ -60,6 +60,7 @@ class Link extends Model
'is_private',
'status',
'check_disabled',
'thumbnail',
];
protected $casts = [

View File

@ -32,20 +32,20 @@ class LinkRepository
*/
public static function create(array $data, bool $flashAlerts = false): Link
{
$linkMeta = HtmlMeta::getFromUrl($data['url'], $flashAlerts);
$linkMeta = (new HtmlMeta)->getFromUrl($data['url'], $flashAlerts);
$data['title'] = $data['title'] ?? $linkMeta['title'];
$data['description'] = $data['description'] ?? $linkMeta['description'];
$data['user_id'] = auth()->user()->id;
$data['icon'] = LinkIconMapper::mapLink($data['url']);
$data['thumbnail'] = $linkMeta['thumbnail'];
// If the meta helper was not successfull, disable future checks and set the status to broken
// If the meta helper was not successful, disable future checks and set the status to broken
if ($linkMeta['success'] === false) {
$data['check_disabled'] = true;
$data['status'] = Link::STATUS_BROKEN;
}
/** @var Link $link */
$link = Link::create($data);
self::processLinkTaxonomies($link, $data);
@ -189,7 +189,7 @@ class LinkRepository
/**
* Tags or lists are passed as comma-delimited strings or integers.
* If integers are passed we assume that the tags or lists are referenced
* by their ID. n that case we try to reetrieve the tag or list by the
* by their ID. n that case we try to retrieve the tag or list by the
* provided ID.
* If tags or lists are passed as strings, we create them and pass the new
* entity to the taxonomy list.

View File

@ -115,7 +115,7 @@ class HistoryEntry extends Component
*
* @param $oldValue
* @param $newValue
* @return null[]
* @return null[]|string[]
*/
protected function processTagsField($oldValue, $newValue)
{

208
composer.lock generated
View File

@ -64,16 +64,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.178.0",
"version": "3.178.5",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "214e3d98c54277cd8965f1cf307dce39631407bf"
"reference": "5a268a20272c0c9171570ed57f68b629e966deab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/214e3d98c54277cd8965f1cf307dce39631407bf",
"reference": "214e3d98c54277cd8965f1cf307dce39631407bf",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5a268a20272c0c9171570ed57f68b629e966deab",
"reference": "5a268a20272c0c9171570ed57f68b629e966deab",
"shasum": ""
},
"require": {
@ -148,9 +148,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.178.0"
"source": "https://github.com/aws/aws-sdk-php/tree/3.178.5"
},
"time": "2021-04-08T18:13:16+00:00"
"time": "2021-04-15T18:12:46+00:00"
},
{
"name": "bacon/bacon-qr-code",
@ -1835,16 +1835,16 @@
},
{
"name": "laravel/fortify",
"version": "v1.7.9",
"version": "v1.7.10",
"source": {
"type": "git",
"url": "https://github.com/laravel/fortify.git",
"reference": "9ba71f3e448ae44370bdfe72f19952e23b4d6191"
"reference": "82c99b6999f7e89f402cfd7eb4074e619382b3b7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/fortify/zipball/9ba71f3e448ae44370bdfe72f19952e23b4d6191",
"reference": "9ba71f3e448ae44370bdfe72f19952e23b4d6191",
"url": "https://api.github.com/repos/laravel/fortify/zipball/82c99b6999f7e89f402cfd7eb4074e619382b3b7",
"reference": "82c99b6999f7e89f402cfd7eb4074e619382b3b7",
"shasum": ""
},
"require": {
@ -1894,20 +1894,20 @@
"issues": "https://github.com/laravel/fortify/issues",
"source": "https://github.com/laravel/fortify"
},
"time": "2021-03-30T21:12:39+00:00"
"time": "2021-04-13T15:05:45+00:00"
},
{
"name": "laravel/framework",
"version": "v8.36.2",
"version": "v8.37.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "0debd8ad6b5aa1f61ccc73910adf049af4ca0444"
"reference": "cf4082973abc796ec285190f0603380021f6d26f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/0debd8ad6b5aa1f61ccc73910adf049af4ca0444",
"reference": "0debd8ad6b5aa1f61ccc73910adf049af4ca0444",
"url": "https://api.github.com/repos/laravel/framework/zipball/cf4082973abc796ec285190f0603380021f6d26f",
"reference": "cf4082973abc796ec285190f0603380021f6d26f",
"shasum": ""
},
"require": {
@ -2062,7 +2062,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2021-04-07T12:37:22+00:00"
"time": "2021-04-13T13:49:49+00:00"
},
{
"name": "league/commonmark",
@ -2167,16 +2167,16 @@
},
{
"name": "league/csv",
"version": "9.7.0",
"version": "9.7.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/csv.git",
"reference": "4cacd9c72c4aa8bdbef43315b2ca25c46a0f833f"
"reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/4cacd9c72c4aa8bdbef43315b2ca25c46a0f833f",
"reference": "4cacd9c72c4aa8bdbef43315b2ca25c46a0f833f",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/0ec57e8264ec92565974ead0d1724cf1026e10c1",
"reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1",
"shasum": ""
},
"require": {
@ -2247,7 +2247,7 @@
"type": "github"
}
],
"time": "2021-03-26T22:08:10+00:00"
"time": "2021-04-17T16:32:08+00:00"
},
{
"name": "league/flysystem",
@ -2780,16 +2780,16 @@
},
{
"name": "opis/closure",
"version": "3.6.1",
"version": "3.6.2",
"source": {
"type": "git",
"url": "https://github.com/opis/closure.git",
"reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5"
"reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opis/closure/zipball/943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5",
"reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5",
"url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6",
"reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6",
"shasum": ""
},
"require": {
@ -2839,9 +2839,9 @@
],
"support": {
"issues": "https://github.com/opis/closure/issues",
"source": "https://github.com/opis/closure/tree/3.6.1"
"source": "https://github.com/opis/closure/tree/3.6.2"
},
"time": "2020-11-07T02:01:34+00:00"
"time": "2021-04-09T13:42:10+00:00"
},
{
"name": "paragonie/constant_time_encoding",
@ -4492,16 +4492,16 @@
},
{
"name": "spatie/laravel-backup",
"version": "6.15.1",
"version": "6.16.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-backup.git",
"reference": "94be6b3bb5248727367a50161be90e6c422558b4"
"reference": "6b2229a07d92c2bb146ad9c5223fc32e9d74830c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-backup/zipball/94be6b3bb5248727367a50161be90e6c422558b4",
"reference": "94be6b3bb5248727367a50161be90e6c422558b4",
"url": "https://api.github.com/repos/spatie/laravel-backup/zipball/6b2229a07d92c2bb146ad9c5223fc32e9d74830c",
"reference": "6b2229a07d92c2bb146ad9c5223fc32e9d74830c",
"shasum": ""
},
"require": {
@ -4566,7 +4566,7 @@
],
"support": {
"issues": "https://github.com/spatie/laravel-backup/issues",
"source": "https://github.com/spatie/laravel-backup/tree/6.15.1"
"source": "https://github.com/spatie/laravel-backup/tree/6.16.0"
},
"funding": [
{
@ -4578,7 +4578,7 @@
"type": "other"
}
],
"time": "2021-03-17T09:41:03+00:00"
"time": "2021-04-15T09:31:32+00:00"
},
{
"name": "spatie/temporary-directory",
@ -7614,16 +7614,16 @@
},
{
"name": "barryvdh/laravel-ide-helper",
"version": "v2.9.3",
"version": "v2.10.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
"reference": "2f61602e7a7f88ad29b0f71355b4bb71396e923b"
"reference": "73b1012b927633a1b4cd623c2e6b1678e6faef08"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/2f61602e7a7f88ad29b0f71355b4bb71396e923b",
"reference": "2f61602e7a7f88ad29b0f71355b4bb71396e923b",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/73b1012b927633a1b4cd623c2e6b1678e6faef08",
"reference": "73b1012b927633a1b4cd623c2e6b1678e6faef08",
"shasum": ""
},
"require": {
@ -7692,7 +7692,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-ide-helper/issues",
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.9.3"
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.10.0"
},
"funding": [
{
@ -7700,7 +7700,7 @@
"type": "github"
}
],
"time": "2021-04-02T14:32:13+00:00"
"time": "2021-04-09T06:17:55+00:00"
},
{
"name": "barryvdh/reflection-docblock",
@ -8072,43 +8072,6 @@
],
"time": "2021-03-25T17:01:18+00:00"
},
{
"name": "dnoegel/php-xdg-base-dir",
"version": "v0.1.1",
"source": {
"type": "git",
"url": "https://github.com/dnoegel/php-xdg-base-dir.git",
"reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
"reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35"
},
"type": "library",
"autoload": {
"psr-4": {
"XdgBaseDir\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "implementation of xdg base directory specification for php",
"support": {
"issues": "https://github.com/dnoegel/php-xdg-base-dir/issues",
"source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1"
},
"time": "2019-12-04T15:06:13+00:00"
},
{
"name": "doctrine/instantiator",
"version": "1.4.0",
@ -8180,16 +8143,16 @@
},
{
"name": "enlightn/enlightn",
"version": "v1.21.0",
"version": "v1.22.0",
"source": {
"type": "git",
"url": "https://github.com/enlightn/enlightn.git",
"reference": "959c9dc6d4dc95c6182569e96fc6e3c48ac538fc"
"reference": "0958011684210838bb50646bedd6a33f73994e7c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/enlightn/enlightn/zipball/959c9dc6d4dc95c6182569e96fc6e3c48ac538fc",
"reference": "959c9dc6d4dc95c6182569e96fc6e3c48ac538fc",
"url": "https://api.github.com/repos/enlightn/enlightn/zipball/0958011684210838bb50646bedd6a33f73994e7c",
"reference": "0958011684210838bb50646bedd6a33f73994e7c",
"shasum": ""
},
"require": {
@ -8257,9 +8220,9 @@
"support": {
"docs": "https://www.laravel-enlightn.com/docs/",
"issues": "https://github.com/enlightn/enlightn/issues",
"source": "https://github.com/enlightn/enlightn/tree/v1.21.0"
"source": "https://github.com/enlightn/enlightn/tree/v1.22.0"
},
"time": "2021-03-23T07:38:44+00:00"
"time": "2021-04-15T15:05:41+00:00"
},
{
"name": "enlightn/security-checker",
@ -8328,16 +8291,16 @@
},
{
"name": "facade/flare-client-php",
"version": "1.6.1",
"version": "1.7.0",
"source": {
"type": "git",
"url": "https://github.com/facade/flare-client-php.git",
"reference": "f2b0969f2d9594704be74dbeb25b201570a98098"
"reference": "6bf380035890cb0a09b9628c491ae3866b858522"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/f2b0969f2d9594704be74dbeb25b201570a98098",
"reference": "f2b0969f2d9594704be74dbeb25b201570a98098",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/6bf380035890cb0a09b9628c491ae3866b858522",
"reference": "6bf380035890cb0a09b9628c491ae3866b858522",
"shasum": ""
},
"require": {
@ -8381,7 +8344,7 @@
],
"support": {
"issues": "https://github.com/facade/flare-client-php/issues",
"source": "https://github.com/facade/flare-client-php/tree/1.6.1"
"source": "https://github.com/facade/flare-client-php/tree/1.7.0"
},
"funding": [
{
@ -8389,20 +8352,20 @@
"type": "github"
}
],
"time": "2021-04-08T08:50:01+00:00"
"time": "2021-04-12T09:30:36+00:00"
},
{
"name": "facade/ignition",
"version": "2.8.2",
"version": "2.8.3",
"source": {
"type": "git",
"url": "https://github.com/facade/ignition.git",
"reference": "cb7f790e6306caeb4a9ffe21e59942b7128cc630"
"reference": "a8201d51aae83addceaef9344592a3b068b5d64d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/ignition/zipball/cb7f790e6306caeb4a9ffe21e59942b7128cc630",
"reference": "cb7f790e6306caeb4a9ffe21e59942b7128cc630",
"url": "https://api.github.com/repos/facade/ignition/zipball/a8201d51aae83addceaef9344592a3b068b5d64d",
"reference": "a8201d51aae83addceaef9344592a3b068b5d64d",
"shasum": ""
},
"require": {
@ -8466,7 +8429,7 @@
"issues": "https://github.com/facade/ignition/issues",
"source": "https://github.com/facade/ignition"
},
"time": "2021-04-08T10:42:53+00:00"
"time": "2021-04-09T20:45:59+00:00"
},
{
"name": "facade/ignition-contracts",
@ -9096,16 +9059,16 @@
},
{
"name": "nunomaduro/collision",
"version": "v5.3.0",
"version": "v5.4.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
"reference": "aca63581f380f63a492b1e3114604e411e39133a"
"reference": "41b7e9999133d5082700d31a1d0977161df8322a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/aca63581f380f63a492b1e3114604e411e39133a",
"reference": "aca63581f380f63a492b1e3114604e411e39133a",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/41b7e9999133d5082700d31a1d0977161df8322a",
"reference": "41b7e9999133d5082700d31a1d0977161df8322a",
"shasum": ""
},
"require": {
@ -9180,20 +9143,20 @@
"type": "patreon"
}
],
"time": "2021-01-25T15:34:13+00:00"
"time": "2021-04-09T13:38:32+00:00"
},
{
"name": "nunomaduro/larastan",
"version": "v0.7.2",
"version": "v0.7.4",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/larastan.git",
"reference": "cb7fa0b5af3738772e3568c0a0c7a080851e281d"
"reference": "0ceef2a39b45be9d7f7dd96192a1721ba5112278"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/larastan/zipball/cb7fa0b5af3738772e3568c0a0c7a080851e281d",
"reference": "cb7fa0b5af3738772e3568c0a0c7a080851e281d",
"url": "https://api.github.com/repos/nunomaduro/larastan/zipball/0ceef2a39b45be9d7f7dd96192a1721ba5112278",
"reference": "0ceef2a39b45be9d7f7dd96192a1721ba5112278",
"shasum": ""
},
"require": {
@ -9257,7 +9220,7 @@
],
"support": {
"issues": "https://github.com/nunomaduro/larastan/issues",
"source": "https://github.com/nunomaduro/larastan/tree/v0.7.2"
"source": "https://github.com/nunomaduro/larastan/tree/v0.7.4"
},
"funding": [
{
@ -9277,7 +9240,7 @@
"type": "patreon"
}
],
"time": "2021-04-08T10:51:16+00:00"
"time": "2021-04-16T08:25:31+00:00"
},
{
"name": "phar-io/manifest",
@ -10098,20 +10061,19 @@
},
{
"name": "psy/psysh",
"version": "v0.10.7",
"version": "v0.10.8",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
"reference": "a395af46999a12006213c0c8346c9445eb31640c"
"reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/a395af46999a12006213c0c8346c9445eb31640c",
"reference": "a395af46999a12006213c0c8346c9445eb31640c",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/e4573f47750dd6c92dca5aee543fa77513cbd8d3",
"reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3",
"shasum": ""
},
"require": {
"dnoegel/php-xdg-base-dir": "0.1.*",
"ext-json": "*",
"ext-tokenizer": "*",
"nikic/php-parser": "~4.0|~3.0|~2.0|~1.3",
@ -10168,9 +10130,9 @@
],
"support": {
"issues": "https://github.com/bobthecow/psysh/issues",
"source": "https://github.com/bobthecow/psysh/tree/v0.10.7"
"source": "https://github.com/bobthecow/psysh/tree/v0.10.8"
},
"time": "2021-03-14T02:14:56+00:00"
"time": "2021-04-10T16:23:39+00:00"
},
{
"name": "roave/security-advisories",
@ -10178,12 +10140,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "aa48fe959b0236eede9c51a38f47df2bb81ef137"
"reference": "593c4de369ca852cf3b86037f19435d47c136448"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/aa48fe959b0236eede9c51a38f47df2bb81ef137",
"reference": "aa48fe959b0236eede9c51a38f47df2bb81ef137",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/593c4de369ca852cf3b86037f19435d47c136448",
"reference": "593c4de369ca852cf3b86037f19435d47c136448",
"shasum": ""
},
"conflict": {
@ -10262,7 +10224,7 @@
"friendsofsymfony/user-bundle": ">=1.2,<1.3.5",
"friendsoftypo3/mediace": ">=7.6.2,<7.6.5",
"fuel/core": "<1.8.1",
"getgrav/grav": "<1.7-beta.8",
"getgrav/grav": "<1.7.11",
"getkirby/cms": ">=3,<3.4.5",
"getkirby/panel": "<2.5.14",
"gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3",
@ -10300,6 +10262,9 @@
"monolog/monolog": ">=1.8,<1.12",
"moodle/moodle": "<3.5.17|>=3.7,<3.7.9|>=3.8,<3.8.8|>=3.9,<3.9.5|>=3.10,<3.10.2",
"namshi/jose": "<2.2",
"neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6",
"neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3",
"neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5",
"nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6",
"nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13",
"nystudio107/craft-seomatic": "<3.3",
@ -10346,6 +10311,7 @@
"propel/propel1": ">=1,<=1.7.1",
"pterodactyl/panel": "<0.7.19|>=1-rc.0,<=1-rc.6",
"pusher/pusher-php-server": "<2.2.1",
"pwweb/laravel-core": "<=0.3.6-beta",
"rainlab/debugbar-plugin": "<3.1",
"robrichards/xmlseclibs": "<3.0.4",
"sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1",
@ -10353,8 +10319,9 @@
"scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11",
"sensiolabs/connect": "<4.2.3",
"serluck/phpwhois": "<=4.2.6",
"shopware/core": "<=6.3.4",
"shopware/platform": "<=6.3.5.1",
"shopware/core": "<=6.3.5.2",
"shopware/platform": "<=6.3.5.2",
"shopware/production": "<=6.3.5.2",
"shopware/shopware": "<5.6.9",
"silverstripe/admin": ">=1.0.3,<1.0.4|>=1.1,<1.1.1",
"silverstripe/assets": ">=1,<1.4.7|>=1.5,<1.5.2",
@ -10431,9 +10398,10 @@
"typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1",
"typo3/cms-core": ">=6.2,<=6.2.56|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<9.5.25|>=10,<10.4.14|>=11,<11.1.1",
"typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1",
"typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5",
"typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4",
"typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6",
"typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3",
"typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1",
"typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5",
"typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10",
"ua-parser/uap-php": "<3.8",
"usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2",
@ -10511,7 +10479,7 @@
"type": "tidelift"
}
],
"time": "2021-04-09T08:01:23+00:00"
"time": "2021-04-16T20:01:44+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@ -42,6 +42,13 @@ return [
* Determines if it should avoid unreadable folders.
*/
'ignore_unreadable_directories' => false,
/*
* This path is used to make directories in resulting zip-file relative
* Set to `null` to include complete absolute path
* Example: base_path()
*/
'relative_path' => null,
],
/*
@ -92,6 +99,14 @@ return [
*/
'database_dump_compressor' => null,
/*
* The file extension used for the database dump files.
*
* If not specified, the file extension will be .archive for MongoDB and .sql for all other databases
* The file extension should be specified without a leading .
*/
'database_dump_file_extension' => '',
'destination' => [
/*
@ -111,11 +126,26 @@ return [
* The directory where the temporary files will be stored.
*/
'temporary_directory' => storage_path('app/backup-temp'),
/*
* The password to be used for archive encryption.
* Set to `null` to disable encryption.
*/
'password' => env('BACKUP_ARCHIVE_PASSWORD'),
/*
* The encryption algorithm to be used for archive encryption.
* You can set it to `null` or `false` to disable encryption.
*
* When set to 'default', we'll use ZipArchive::EM_AES_256 if it is
* available on your system.
*/
'encryption' => 'default',
],
/*
* You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'.
* For Slack you need to install guzzlehttp/guzzle and laravel/slack-notification-channel.
* For Slack you need to install laravel/slack-notification-channel.
*
* You can also use your own notification classes, just make sure the class is named after one of
* the `Spatie\Backup\Events` classes.

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddThumbnailColumnToLinksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('links', function (Blueprint $table) {
$table->string('thumbnail', 255)->nullable()->default(null)->after('icon');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('links', function (Blueprint $table) {
$table->dropColumn('thumbnail');
});
}
}

22
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "linkace",
"version": "1.5.0",
"version": "1.6.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -3402,9 +3402,9 @@
}
},
"csv-parse": {
"version": "4.15.3",
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.15.3.tgz",
"integrity": "sha512-jlTqDvLdHnYMSr08ynNfk4IAUSJgJjTKy2U5CQBSu4cN9vQOJonLVZP4Qo4gKKrIgIQ5dr07UwOJdi+lRqT12w=="
"version": "4.15.4",
"resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.15.4.tgz",
"integrity": "sha512-OdBbFc0yZhOm17lSxqkirrHlFFVpKRT0wp4DAGoJelsP3LbGzV9LNr7XmM/lrr0uGkCtaqac9UhP8PDHXOAbMg=="
},
"debug": {
"version": "4.3.1",
@ -6687,9 +6687,9 @@
"dev": true
},
"postcss": {
"version": "8.2.9",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.9.tgz",
"integrity": "sha512-b+TmuIL4jGtCHtoLi+G/PisuIl9avxs8IZMSmlABRwNz5RLUUACrC+ws81dcomz1nRezm5YPdXiMEzBEKgYn+Q==",
"version": "8.2.10",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.10.tgz",
"integrity": "sha512-b/h7CPV7QEdrqIxtAf2j31U5ef05uBDuvoXv6L51Q4rcS1jdlXAVKJv+atCFdUXYl9dyTHGyoMzIepwowRJjFw==",
"dev": true,
"requires": {
"colorette": "^1.2.2",
@ -9076,12 +9076,12 @@
"dev": true
},
"sass": {
"version": "1.32.8",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.32.8.tgz",
"integrity": "sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==",
"version": "1.32.10",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.32.10.tgz",
"integrity": "sha512-Nx0pcWoonAkn7CRp0aE/hket1UP97GiR1IFw3kcjV3pnenhWgZEWUf0ZcfPOV2fK52fnOcK3JdC/YYZ9E47DTQ==",
"dev": true,
"requires": {
"chokidar": ">=2.0.0 <4.0.0"
"chokidar": ">=3.0.0 <4.0.0"
}
},
"sass-loader": {

View File

@ -1,6 +1,6 @@
{
"name": "linkace",
"version": "1.5.0",
"version": "1.6.0",
"description": "A small, selfhosted bookmark manager with advanced features, built with Laravel and Docker",
"homepage": "https://github.com/Kovah/LinkAce",
"repository": {
@ -14,8 +14,8 @@
},
"devDependencies": {
"laravel-mix": "^6.0.16",
"postcss": "^8.2.9",
"sass": "^1.32.8",
"postcss": "^8.2.10",
"sass": "^1.32.10",
"sass-loader": "^10.1.1"
},
"dependencies": {

View File

@ -165,3 +165,39 @@ code {
#loader {
display: none !important;
}
.link-thumbnail {
box-shadow: inset 0 0 1px $secondary;
}
.link-thumbnail-detail {
width: 100%;
height: auto;
padding-bottom: 52.5%;
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
@include media-breakpoint-up('lg') {
width: 180px;
height: 93px;
padding-bottom: 0;
}
}
.link-thumbnail-list-holder {
width: 180px;
height: 93px;
//@include media-breakpoint-up('lg') {
// width: 180px;
//}
.link-thumbnail-list {
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-position: center center;
background-size: cover !important;
}
}

View File

@ -1,13 +0,0 @@
@extends('layouts.app')
@section('content')
@include('actions.settings.partials.system.updates')
@include('actions.settings.partials.system.cron')
@include('actions.settings.partials.system.general-settings')
@include('actions.settings.partials.system.guest-settings')
@endsection

View File

@ -1,17 +0,0 @@
@extends('layouts.app')
@section('content')
@include('actions.settings.partials.user.bookmarklet')
@include('actions.settings.partials.user.api')
@include('actions.settings.partials.user.account-settings')
@include('actions.settings.partials.user.change-pw')
@include('actions.settings.partials.user.two-factor')
@include('actions.settings.partials.user.app-settings')
@endsection

View File

@ -138,7 +138,7 @@
@lang('search.no_results')
</div>
@else
@include('actions.search.partials.table', ['results' => $results])
@include('app.search.partials.table', ['results' => $results])
@endif
</div>

View File

@ -97,9 +97,9 @@
<div class="col-12 col-sm-8 col-md-6"></div>
</div>
@include('actions.settings.partials.system.guest.dark-mode')
@include('app.settings.partials.system.guest.dark-mode')
@include('actions.settings.partials.system.guest.sharing')
@include('app.settings.partials.system.guest.sharing')
<button type="submit" class="btn btn-primary">
<x-icon.save class="mr-2"/> @lang('settings.save_settings')

View File

@ -215,13 +215,13 @@
</div>
</div>
@include('actions.settings.partials.user.app-settings.archive-backups')
@include('app.settings.partials.user.app-settings.archive-backups')
@include('actions.settings.partials.user.app-settings.privacy')
@include('app.settings.partials.user.app-settings.privacy')
@include('actions.settings.partials.user.app-settings.dark-mode')
@include('app.settings.partials.user.app-settings.dark-mode')
@include('actions.settings.partials.user.app-settings.sharing')
@include('app.settings.partials.user.app-settings.sharing')
<button type="submit" class="btn btn-primary">
<x-icon.save class="mr-2"/> @lang('settings.save_settings')

View File

@ -0,0 +1,13 @@
@extends('layouts.app')
@section('content')
@include('app.settings.partials.system.updates')
@include('app.settings.partials.system.cron')
@include('app.settings.partials.system.general-settings')
@include('app.settings.partials.system.guest-settings')
@endsection

View File

@ -0,0 +1,17 @@
@extends('layouts.app')
@section('content')
@include('app.settings.partials.user.bookmarklet')
@include('app.settings.partials.user.api')
@include('app.settings.partials.user.account-settings')
@include('app.settings.partials.user.change-pw')
@include('app.settings.partials.user.two-factor')
@include('app.settings.partials.user.app-settings')
@endsection

View File

@ -25,7 +25,7 @@
</div>
<div class="card-body">
@includeWhen($links->isNotempty(), 'actions.trash.partials.link-table', ['links' => $links])
@includeWhen($links->isNotempty(), 'app.trash.partials.link-table', ['links' => $links])
@if($links->isEmpty())
<small class="text-muted">@lang('trash.delete_no_entries')</small>
@endif
@ -50,7 +50,7 @@
</div>
<div class="card-body">
@includeWhen($lists->isNotEmpty(), 'actions.trash.partials.list-table', ['lists' => $lists])
@includeWhen($lists->isNotEmpty(), 'app.trash.partials.list-table', ['lists' => $lists])
@if($lists->isEmpty())
<small class="text-muted">@lang('trash.delete_no_entries')</small>
@endif
@ -75,7 +75,7 @@
</div>
<div class="card-body">
@includeWhen($tags->isNotEmpty(), 'actions.trash.partials.tag-table', ['tags' => $tags])
@includeWhen($tags->isNotEmpty(), 'app.trash.partials.tag-table', ['tags' => $tags])
@if($tags->isEmpty())
<small class="text-muted">@lang('trash.delete_no_entries')</small>
@endif
@ -100,7 +100,7 @@
</div>
<div class="card-body">
@includeWhen($notes->isNotEmpty(), 'actions.trash.partials.note-table', ['notes' => $notes])
@includeWhen($notes->isNotEmpty(), 'app.trash.partials.note-table', ['notes' => $notes])
@if($notes->isEmpty())
<small class="text-muted">@lang('trash.delete_no_entries')</small>

View File

@ -1,7 +1,14 @@
<div class="card mb-4">
<div class="card-header">
<div class="d-flex align-items-top flex-wrap">
<div class="d-flex align-items-top flex-wrap flex-md-nowrap">
@if($link->thumbnail)
<div class="d-flex justify-content-center mr-2 mb-2 mb-md-0 link-thumbnail-list-holder">
<a href="{{ $link->url }}" {!! linkTarget() !!} class="rounded d-block link-thumbnail link-thumbnail-list"
style="background-image: url('{{ $link->thumbnail }}');">
</a>
</div>
@endif
<div class="mr-2 mw-100">
@if($link->is_private)
<span>

View File

@ -7,7 +7,13 @@
<div class="col-12 col-md-8">
<div class="card">
<div class="card-body">
<div class="d-sm-flex mb-3">
<div class="d-flex flex-column flex-lg-row mb-3">
@if($link->thumbnail)
<a href="{{ $link->url }}" {!! linkTarget() !!}
class="rounded d-block mt-lg-1 mr-lg-2 align-self-center link-thumbnail link-thumbnail-detail"
style="background-image: url('{{ $link->thumbnail }}') ;">
</a>
@endif
<div class="d-sm-inline-block mt-1 mb-2 mb-sm-0">
{!! $link->getIcon('mr-1 mr-sm-2') !!}
@if($link->is_private)

View File

@ -72,7 +72,7 @@ Route::group(['middleware' => ['auth']], function () {
Route::resource('lists', ListController::class);
Route::resource('tags', TagController::class);
Route::resource('notes', NoteController::class)
->except(['index', 'show']);
->except(['index', 'show', 'create']);
Route::post('links/toggle-check/{link}', [LinkController::class, 'updateCheckToggle'])
->name('links.toggle-check');

View File

@ -11,35 +11,33 @@ use Tests\TestCase;
class HtmlMetaHelperTest extends TestCase
{
/**
* Test the titleFromURL() helper function with a valid URL
* Will return the title of the DuckDuckGo frontpage: "DuckDuckGo"
/*
* Test the HtmlMeta helper with a regular website containing some meta information. Must properly return the
* information extracted from the meta.
*/
public function testTitleFromValidURL(): void
public function testMetaFromValidURL(): void
{
$testHtml = '<!DOCTYPE html><head>' .
'<title>DuckDuckGo</title>' .
'<meta name="test" content="Bla">' .
'<meta name="description" content="This an example description">' .
'<meta property="og:image" content="https://duckduckgo.com/assets/logo_social-media.png">' .
'</head></html>';
Http::fake([
'*' => Http::response($testHtml, 200),
]);
Http::fake(['*' => Http::response($testHtml)]);
$url = 'https://duckduckgo.com/';
$result = HtmlMeta::getFromUrl($url);
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('DuckDuckGo', $result['title']);
$this->assertEquals('This an example description', $result['description']);
$this->assertEquals('https://duckduckgo.com/assets/logo_social-media.png', $result['thumbnail']);
$this->assertTrue($result['success']);
}
/**
* Test the titleFromURL() helper function with a valid URL
* Will return the title of the DuckDuckGo frontpage: "DuckDuckGo".
/*
* Test the HtmlMeta helper with an alternative description provided by the og:description tag.
*/
public function testAlternativeDescriptionFromValidURL(): void
{
@ -48,42 +46,81 @@ class HtmlMetaHelperTest extends TestCase
'<meta property="og:description" content="This an example description">' .
'</head></html>';
Http::fake([
'*' => Http::response($testHtml, 200),
]);
Http::fake(['*' => Http::response($testHtml)]);
$url = 'https://duckduckgo.com/';
$result = HtmlMeta::getFromUrl($url);
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('DuckDuckGo', $result['title']);
$this->assertEquals('This an example description', $result['description']);
$this->assertTrue($result['success']);
}
/**
* Test the titleFromURL() helper function with an invalid URL
* Will return just the host of the given URL.
public function testThumbnailWithoutHostFromValidURL(): void
{
$testHtml = '<!DOCTYPE html><head>' .
'<meta property="og:image" content="/assets/logo_social-media.png">' .
'</head></html>';
Http::fake(['*' => Http::response($testHtml)]);
$url = 'https://duckduckgo.com/about-us?utm_source=foo';
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertEquals('https://duckduckgo.com/assets/logo_social-media.png', $result['thumbnail']);
$this->assertTrue($result['success']);
}
/*
* Test the HtmlMeta helper with a YouTube link. Must return a special YouTube thumbnail.
*/
public function testYoutubeThumbnailFromValidURL(): void
{
$testHtml = '<!DOCTYPE html><head>' .
'<title>YouTube</title>' .
'</head></html>';
Http::fake(['*' => Http::response($testHtml)]);
// Regular YouTube link
$url = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('YouTube', $result['title']);
$this->assertEquals('https://img.youtube.com/vi/dQw4w9WgXcQ/mqdefault.jpg', $result['thumbnail']);
$this->assertTrue($result['success']);
// Short Youtu.be sharing link
$url = 'https://youtu.be/dQw4w9WgXcQ';
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('YouTube', $result['title']);
$this->assertEquals('https://img.youtube.com/vi/dQw4w9WgXcQ/mqdefault.jpg', $result['thumbnail']);
$this->assertTrue($result['success']);
}
/*
* Test the HtmlMeta helper with an invalid URL. Must return the hostname as the title.
*/
public function testTitleFromInvalidURL(): void
{
$url = 'https://duckduckgogo.comcom/';
Http::fake([
'*' => Http::response(null, 404),
]);
Http::fake(['*' => Http::response('', 404)]);
$result = HtmlMeta::getFromUrl($url);
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('duckduckgogo.comcom', $result['title']);
$this->assertFalse($result['success']);
}
/**
* Test the titleFromURL() helper function with an invalid URL
* Will return just the host of the given URL.
/*
* Test the HtmlMeta helper with an invalid URL. Must return the hostname as the title.
*/
public function testTitleFromUrlWithoutProtocol(): void
{
@ -93,17 +130,15 @@ class HtmlMetaHelperTest extends TestCase
'*' => Http::response(null, 404),
]);
$result = HtmlMeta::getFromUrl($url);
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('duckduckgo.com/about-us', $result['title']);
$this->assertFalse($result['success']);
}
/**
* Test the titleFromURL() helper function with an valid URL that returns
* a certificate error.
* Will return just the host of the given URL and issue a new flash message.
/*
* Test the HtmlMeta helper with an URL returning a certificate error. Must return the hostname as the title.
*/
public function testRequestError(): void
{
@ -116,7 +151,7 @@ class HtmlMetaHelperTest extends TestCase
);
});
$result = HtmlMeta::getFromUrl($url, true);
$result = (new HtmlMeta)->getFromUrl($url, true);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('self-signed.badssl.com', $result['title']);
@ -129,11 +164,9 @@ class HtmlMetaHelperTest extends TestCase
);
}
/**
* Test the titleFromURL() helper function with an valid URL that is not
* accessible due to connection errors, such as a refused connection for
* a specific port.
* Will return just the host of the given URL and issue a new flash message.
/*
* Test the HtmlMeta helper with an URL that is not accessible due to connection errors, such as a refused
* connection for a specific port. Must return the hostname as the title.
*/
public function testConnectionError(): void
{
@ -145,7 +178,7 @@ class HtmlMetaHelperTest extends TestCase
);
});
$result = HtmlMeta::getFromUrl($url, true);
$result = (new HtmlMeta)->getFromUrl($url, true);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('192.168.0.123', $result['title']);
@ -171,13 +204,11 @@ class HtmlMetaHelperTest extends TestCase
hex2bin('3c7469746c653ecfe8eae0e1f33c2f7469746c653e') .
'</head></html>';
Http::fake([
'*' => Http::response($testHtml, 200),
]);
Http::fake(['*' => Http::response($testHtml)]);
$url = 'https://duckduckgo.com/';
$result = HtmlMeta::getFromUrl($url);
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('Пикабу', $result['title']);
@ -196,13 +227,11 @@ class HtmlMetaHelperTest extends TestCase
hex2bin('3c7469746c653ecfe8eae0e1f33c2f7469746c653e') .
'</head></html>';
Http::fake([
'*' => Http::response($testHtml, 200),
]);
Http::fake(['*' => Http::response($testHtml)]);
$url = 'https://duckduckgo.com/';
$result = HtmlMeta::getFromUrl($url);
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('duckduckgo.com', $result['title']);
@ -220,12 +249,10 @@ class HtmlMetaHelperTest extends TestCase
'<meta charset="utf-8,windows-1251">' .
'</head></html>';
Http::fake([
'*' => Http::response($testHtml, 200),
]);
Http::fake(['*' => Http::response($testHtml)]);
$url = 'https://duckduckgo.com/';
$result = HtmlMeta::getFromUrl($url);
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertArrayHasKey('title', $result);
$this->assertEquals('duckduckgo.com', $result['title']);
@ -253,7 +280,7 @@ class HtmlMetaHelperTest extends TestCase
$url = 'https://encoding-test.com/';
$result = HtmlMeta::getFromUrl($url);
$result = (new HtmlMeta)->getFromUrl($url);
$this->assertArrayHasKey('description', $result);
$this->assertEquals('Qualität', $result['description']);

View File

@ -16,10 +16,7 @@ class UpdateCheckTest extends TestCase
public function testSuccessfulCheck(): void
{
Http::fake([
'github.com/*' => Http::response(
[['tag_name' => 'v100.0.0']],
200
),
'*' => Http::response('v100.0.0'),
]);
$result = UpdateHelper::checkForUpdates();
@ -34,10 +31,7 @@ class UpdateCheckTest extends TestCase
public function testSuccessfulCheckWithoutVersion(): void
{
Http::fake([
'github.com/*' => Http::response(
[['tag_name' => 'v0.0.0']],
200
),
'*' => Http::response('v0.0.0'),
]);
$result = UpdateHelper::checkForUpdates();
@ -52,7 +46,7 @@ class UpdateCheckTest extends TestCase
public function testUpdateCheckWithNetworkError(): void
{
Http::fake([
'github.com/*' => Http::response([], 404),
'*' => Http::response('', 404),
]);
$result = UpdateHelper::checkForUpdates();

View File

@ -2,8 +2,6 @@
namespace Tests\Models;
use App\Helper\HtmlMeta;
use App\Helper\LinkIconMapper;
use App\Models\User;
use App\Repositories\LinkRepository;
use Illuminate\Foundation\Testing\DatabaseMigrations;
@ -23,11 +21,14 @@ class LinkCreateTest extends TestCase
{
parent::setUp();
$testHtml = '<!DOCTYPE html><head><title>Google</title></head></html>';
$testHtml = '<!DOCTYPE html><head>' .
'<title>DuckDuckGo</title>' .
'<meta name="test" content="Bla">' .
'<meta name="description" content="This an example description">' .
'<meta property="og:image" content="https://duckduckgo.com/assets/logo_social-media.png">' .
'</head></html>';
Http::fake([
'*' => Http::response($testHtml, 200),
]);
Http::fake(['*' => Http::response($testHtml)]);
$this->user = User::factory()->create();
}
@ -36,31 +37,32 @@ class LinkCreateTest extends TestCase
{
$this->be($this->user);
$url = 'https://google.com/';
$meta = HtmlMeta::getFromUrl($url);
$url = 'https://duckduckgo.com/';
$originalData = [
'url' => $url,
'title' => $meta['title'],
'description' => $meta['description'],
'title' => null,
'description' => null,
'is_private' => false,
];
$link = LinkRepository::create($originalData);
$automatedData = [
$assertedData = [
'id' => $link->id,
'icon' => LinkIconMapper::mapLink($url),
'user_id' => auth()->user()->id,
'url' => $url,
'title' => 'DuckDuckGo',
'description' => 'This an example description',
'icon' => 'link',
'thumbnail' => 'https://duckduckgo.com/assets/logo_social-media.png',
'is_private' => 0,
'user_id' => 1,
'status' => 1,
'created_at' => $link->created_at,
'updated_at' => $link->updated_at,
'deleted_at' => null,
];
$assertedData = array_merge($automatedData, $originalData);
$this->assertDatabaseHas('links', $assertedData);
}
}