added content collector and pagination

plugged in the dash index template which required grabbing markdown
pages and converting them to data the system can use and then pagination
that is used to sort content into pages

start page now switches from login screen to index based on session, but
that might be changes so it's a bit more clean to work with middleware
This commit is contained in:
ro 2024-03-03 17:49:05 -06:00
parent c77eeafb2c
commit 2395278893
No known key found for this signature in database
GPG key ID: 29B551CDBD4D3B50
9 changed files with 736 additions and 92 deletions

View file

@ -2,43 +2,38 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\SettingsService;
use App\Services\AuthService;
use Illuminate\Http\Request;
use App\Services\PaginateService;
class DashController extends Controller
{
protected $files = [];
protected $settings;
protected $auth;
protected $pages;
public function __construct(
SettingsService $settingsService,
AuthService $authService
AuthService $authService,
PaginateService $paginateService,
) {
$this->read(env('PAGES_PATH'));
$this->settings = $settingsService;
$this->auth = $authService;
$this->pages = $paginateService;
}
public function start(Request $request)
{
$status = session('handle') !== null ? true : false;
$result = [];
if ($status) {
$result = $this->pages->getPage(1, 4);
//var_dump($result['pages'][1]['media'][0]['type']);
}
return view('back.start', [
"status" => (session('handle') !== null ? true : false),
"status" => $status,
"result" => $result,
"title" => "Fipamo Dash"
]);
}
public function read($folder)
{
$folders = glob("$folder/*", GLOB_ONLYDIR);
foreach ($folders as $folder) {
//$this->files[] = $folder . "/";
$this->read($folder);
}
$files = array_filter(glob("$folder/*md"), 'is_file');
foreach ($files as $file) {
$this->files[] = $file;
}
}
}

View file

@ -5,6 +5,8 @@ namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\SettingsService;
use App\Services\AuthService;
use App\Services\ContentService;
use App\Services\PaginateService;
class FipamoServiceProvider extends ServiceProvider
{
@ -20,6 +22,14 @@ class FipamoServiceProvider extends ServiceProvider
$this->app->bind(AuthService::class, function ($app) {
return new AuthService(new SettingsService());
});
$this->app->bind(ContentService::class, function ($app) {
return new ContentService();
});
$this->app->bind(PaginateService::class, function ($app) {
return new PaginateService(new ContentService());
});
}
/**

View file

@ -0,0 +1,195 @@
<?php
namespace App\Services;
use HtmlSanitizer\SanitizerBuilder;
use League\CommonMark\MarkdownConverter;
use League\CommonMark\Environment\Environment;
use HtmlSanitizer\Extension\Basic\BasicExtension;
use HtmlSanitizer\Extension\Listing\ListExtension;
use HtmlSanitizer\Extension\Iframe\IframeExtension;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\Extension\Attributes\AttributesExtension;
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
class ContentService
{
protected $files = [];
protected $config = [];
public function __construct()
{
$this->loadPages(env('PAGES_PATH'));
}
public function loadPages($folder)
{
$folders = glob("$folder/*", GLOB_ONLYDIR);
foreach ($folders as $folder) {
//$this->files[] = $folder . "/";
$this->loadPages($folder);
}
$files = array_filter(glob("$folder/*md"), 'is_file');
foreach ($files as $file) {
$this->files[] = $file;
}
}
public function loadAllPages()
{
$environment = new Environment($this->config);
$environment->addExtension(new CommonMarkCoreExtension());
// Add the extension
$environment->addExtension(new FrontMatterExtension());
//Add Strikethrough rendering
$environment->addExtension(new StrikethroughExtension());
//add attributes to elements in markdown
$environment->addExtension(new AttributesExtension());
//add table rendering
$environment->addExtension(new TableExtension());
// Instantiate the converter engine and start converting some Markdown!
$converter = new MarkdownConverter($environment);
$contents = [];
foreach ($this->files as $file) {
//get meta and html from file
$result = $converter->convertToHtml(file_get_contents($file));
$meta = [];
if ($result instanceof RenderedContentWithFrontMatter) {
$meta = $result->getFrontMatter();
}
//get raw markdown from file
$frontMatterExtension = new FrontMatterExtension();
$parsed = $frontMatterExtension
->getFrontMatterParser()
->parse(file_get_contents($file));
//never trust the front end. clean it up
//add what sanitizer extensions we need manually
$builder = new SanitizerBuilder();
$builder->registerExtension(new BasicExtension());
$builder->registerExtension(new IframeExtension());
$builder->registerExtension(new ListExtension());
//just add it straight because classname is already in use
$builder->registerExtension(new \HtmlSanitizer\Extension\Table\TableExtension());
//relative-a and relative-image
$builder->registerExtension(
new \HtmlSanitizer\Extension\Relative\A\AExtension()
);
$builder->registerExtension(
new \HtmlSanitizer\Extension\Relative\Image\ImageExtension()
);
$detergent = [
'extensions' => ['basic', 'list', 'relative-a', 'relative-image', 'iframe', 'table'],
'tags' => [
'div' => [
'allowed_attributes' => ['class', 'title', 'id', 'style'],
],
'img' => [
'allowed_attributes' => ['src', 'alt', 'title', 'class'],
],
'iframe' => [
'allowed_attributes' => ['height', 'width', 'title', 'src'],
],
],
];
$sanitizer = $builder->build($detergent);
$scrubbed = $sanitizer->sanitize($result->getContent());
if (isset($meta['feature'])) {
$featureList = explode(',', $meta['feature']);
} else {
$featureList = "";
}
$docs = '';
if (isset($meta['files'])) {
$fileList = explode(',', $meta['files']);
$docs = $meta['files'];
} else {
$fileList = [];
$docs = '';
}
$media = [];
$files = [];
if ($featureList != '') {
foreach ($featureList as $file) {
$item = trim($file);
$ext = pathinfo($item, PATHINFO_EXTENSION);
if ($item != null || $item != '') {
array_push($media, ['file' => $item, 'type' => trim($ext)]);
}
}
}
if ($fileList != "") {
foreach ($fileList as $file) {
$item = trim($file);
$ext = pathinfo($item, PATHINFO_EXTENSION);
if ($item != null || $item != '') {
array_push($files, ['file' => $item, 'type' => trim($ext)]);
}
}
}
//sort attributes into page object
$page = [
'id' => $meta['id'],
'uuid' => $meta['uuid'],
'title' => $meta['title'],
'feature' => $meta['feature'],
'files' => $docs,
'path' => $meta['path'],
'layout' => $meta['layout'],
'tags' => $meta['tags'],
'author' => $meta['author'],
'created' => date('Y M D d', $meta['created']),
'updated' => date('Y M D d', $meta['updated']),
'rawCreated' => $meta['created'],
'rawUpdated' => $meta['updated'],
'createdYear' => date('Y', $meta['created']),
'createdMonth' => date('m', $meta['created']),
'deleted' => $meta['deleted'],
'menu' => $meta['menu'],
'featured' => $meta['featured'],
'published' => $meta['published'],
'slug' => $meta['slug'],
'filePath' => $file,
'content' => $parsed->getContent(),
'html' => $scrubbed,
'media' => $media,
'docs' => $files
];
//checks for duplicates
$uuid = $meta['uuid'];
$found = current(
array_filter($contents, function ($item) use ($uuid) {
return isset($item['uuid']) && $uuid == $item['uuid'];
})
);
// if uuid is not present, add it
if (!$found) {
array_push($contents, $page);
}
}
$collection = collect($contents);
$sorted = $collection->sortBy([
['id', 'desc'],
]);
$sorted->values()->all();
return $sorted;
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace App\Services;
use function _\filter;
class PaginateService
{
protected $content;
public function __construct(ContentService $contentService)
{
$this->content = $contentService;
}
public function getPage(int $page, int $limit, string $sort = null)
{
$content = $this->content->loadAllPages();
$published = filter($content, function ($item) {
return $item['published'] == true && $item['deleted'] == false;
});
$deleted = filter($content, function ($item) {
return $item['deleted'] == true;
});
// $all = $content;
$all = filter($content, function ($item) {
return $item['deleted'] == false;
});
$filter = isset($sort) ? $sort : 'all';
switch ($filter) {
case 'published':
$filtered = $published;
break;
case 'deleted':
$filtered = $deleted;
break;
default:
$filtered = $all;
break;
}
$numOfPages = ceil(count($filtered) / ($limit + 1));
$folder = [];
if (count($filtered) != 0) {
if (count($filtered) < $limit) {
$limit = count($filtered) - 1;
}
$range = $page * $limit - $limit;
if ($range != 0) {
$range = $range + 1;
}
for ($i = 0; $i <= $limit; ++$i) {
if (isset($filtered[$i + $range])) {
array_push($folder, $filtered[$i + $range]);
} else {
// chill out
}
}
}
$prev = $page - 1;
if ($prev <= 0) {
$prev = $numOfPages;
}
$next = $page + 1;
if ($next > $numOfPages) {
$next = 1;
}
return [
'pages' => $folder,
'numOfPages' => $numOfPages,
'entryCount' => count($filtered),
'paginate' => [
'sort' => $sort,
'nextPage' => $next,
'prevPage' => $prev,
],
'stats' => [
'all' => count($all),
'published' => count($published),
'deleted' => count($deleted),
],
];
}
}

View file

@ -21,7 +21,12 @@
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8",
"lodash-php/lodash-php": "^0.09.0"
"league/commonmark": "^2.4",
"lodash-php/lodash-php": "^0.09.0",
"mnapoli/front-yaml": "^2.0",
"olegatro/html-sanitizer-relative": "^1.0",
"symfony/yaml": "^7.0",
"tgalopin/html-sanitizer": "^1.5"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",

409
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7d751338a52e6d29523f9e2e76fc0397",
"content-hash": "35ea09eaea488e5d0e0c4a22059c367c",
"packages": [
{
"name": "brick/math",
@ -1894,6 +1894,76 @@
],
"time": "2024-01-28T23:22:08+00:00"
},
{
"name": "league/uri-parser",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri-parser.git",
"reference": "671548427e4c932352d9b9279fdfa345bf63fa00"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri-parser/zipball/671548427e4c932352d9b9279fdfa345bf63fa00",
"reference": "671548427e4c932352d9b9279fdfa345bf63fa00",
"shasum": ""
},
"require": {
"php": ">=7.0.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.0",
"phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-phpunit": "^0.9.4",
"phpstan/phpstan-strict-rules": "^0.9.0",
"phpunit/phpunit": "^6.0"
},
"suggest": {
"ext-intl": "Allow parsing RFC3987 compliant hosts",
"league/uri-schemes": "Allow validating and normalizing URI parsing results"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"League\\Uri\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "nyamsprod@gmail.com",
"homepage": "https://nyamsprod.com"
}
],
"description": "userland URI parser RFC 3986 compliant",
"homepage": "https://github.com/thephpleague/uri-parser",
"keywords": [
"parse_url",
"parser",
"rfc3986",
"rfc3987",
"uri",
"url"
],
"support": {
"issues": "https://github.com/thephpleague/uri-parser/issues",
"source": "https://github.com/thephpleague/uri-parser/tree/master"
},
"abandoned": true,
"time": "2018-11-22T07:55:51+00:00"
},
{
"name": "lodash-php/lodash-php",
"version": "0.09",
@ -1951,6 +2021,110 @@
},
"time": "2024-02-01T07:59:55+00:00"
},
{
"name": "masterminds/html5",
"version": "2.8.1",
"source": {
"type": "git",
"url": "https://github.com/Masterminds/html5-php.git",
"reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf",
"reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf",
"shasum": ""
},
"require": {
"ext-dom": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Masterminds\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matt Butcher",
"email": "technosophos@gmail.com"
},
{
"name": "Matt Farina",
"email": "matt@mattfarina.com"
},
{
"name": "Asmir Mustafic",
"email": "goetas@gmail.com"
}
],
"description": "An HTML5 parser and serializer.",
"homepage": "http://masterminds.github.io/html5-php",
"keywords": [
"HTML5",
"dom",
"html",
"parser",
"querypath",
"serializer",
"xml"
],
"support": {
"issues": "https://github.com/Masterminds/html5-php/issues",
"source": "https://github.com/Masterminds/html5-php/tree/2.8.1"
},
"time": "2023-05-10T11:58:31+00:00"
},
{
"name": "mnapoli/front-yaml",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/mnapoli/FrontYAML.git",
"reference": "d42d84159f3725d50f7bda953ed90185b3c41cc4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mnapoli/FrontYAML/zipball/d42d84159f3725d50f7bda953ed90185b3c41cc4",
"reference": "d42d84159f3725d50f7bda953ed90185b3c41cc4",
"shasum": ""
},
"require": {
"league/commonmark": "^2.0",
"php": "^7.4|^8.0",
"symfony/yaml": "^4.0|^5.0|^6.0|^7.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Mni\\FrontYAML\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"support": {
"source": "https://github.com/mnapoli/FrontYAML/tree/2.0.3"
},
"time": "2024-02-07T14:42:22+00:00"
},
{
"name": "monolog/monolog",
"version": "3.5.0",
@ -2451,6 +2625,48 @@
],
"time": "2023-02-08T01:06:31+00:00"
},
{
"name": "olegatro/html-sanitizer-relative",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/olegatro/html-sanitizer-relative.git",
"reference": "1a4d4683c0c162653da6dcfe6050476dd8bfc026"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/olegatro/html-sanitizer-relative/zipball/1a4d4683c0c162653da6dcfe6050476dd8bfc026",
"reference": "1a4d4683c0c162653da6dcfe6050476dd8bfc026",
"shasum": ""
},
"require": {
"ext-dom": "*",
"php": ">=7.1",
"tgalopin/html-sanitizer": "^1.4"
},
"type": "library",
"autoload": {
"psr-4": {
"HtmlSanitizer\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Oleg Scherbakov",
"email": "shcherbakov.oleg88@gmail.com"
}
],
"description": "Extension for html-sanitizer library by allowing relative urls in the A and Image tags",
"support": {
"issues": "https://github.com/olegatro/html-sanitizer-relative/issues",
"source": "https://github.com/olegatro/html-sanitizer-relative/tree/1.0.0"
},
"time": "2021-02-05T13:39:44+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.2",
@ -5917,6 +6133,126 @@
],
"time": "2024-02-15T11:23:52+00:00"
},
{
"name": "symfony/yaml",
"version": "v7.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "2d4fca631c00700597e9442a0b2451ce234513d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/2d4fca631c00700597e9442a0b2451ce234513d3",
"reference": "2d4fca631c00700597e9442a0b2451ce234513d3",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"symfony/console": "<6.4"
},
"require-dev": {
"symfony/console": "^6.4|^7.0"
},
"bin": [
"Resources/bin/yaml-lint"
],
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v7.0.3"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-01-23T15:02:46+00:00"
},
{
"name": "tgalopin/html-sanitizer",
"version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/tgalopin/html-sanitizer.git",
"reference": "5d02dcb6f2ea4f505731eac440798caa1b3b0913"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tgalopin/html-sanitizer/zipball/5d02dcb6f2ea4f505731eac440798caa1b3b0913",
"reference": "5d02dcb6f2ea4f505731eac440798caa1b3b0913",
"shasum": ""
},
"require": {
"ext-dom": "*",
"league/uri-parser": "^1.4.1",
"masterminds/html5": "^2.4",
"php": ">=7.1",
"psr/log": "^1.0|^2.0|^3.0"
},
"require-dev": {
"phpunit/phpunit": "^7.4",
"symfony/var-dumper": "^4.1"
},
"type": "library",
"autoload": {
"psr-4": {
"HtmlSanitizer\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Titouan Galopin",
"email": "galopintitouan@gmail.com"
}
],
"description": "Sanitize untrustworthy HTML user input",
"support": {
"issues": "https://github.com/tgalopin/html-sanitizer/issues",
"source": "https://github.com/tgalopin/html-sanitizer/tree/1.5.0"
},
"abandoned": "symfony/html-sanitizer",
"time": "2021-09-14T08:27:50+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "v2.2.7",
@ -8208,77 +8544,6 @@
],
"time": "2024-02-09T16:08:40+00:00"
},
{
"name": "symfony/yaml",
"version": "v7.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "2d4fca631c00700597e9442a0b2451ce234513d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/2d4fca631c00700597e9442a0b2451ce234513d3",
"reference": "2d4fca631c00700597e9442a0b2451ce234513d3",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"symfony/console": "<6.4"
},
"require-dev": {
"symfony/console": "^6.4|^7.0"
},
"bin": [
"Resources/bin/yaml-lint"
],
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v7.0.3"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-01-23T15:02:46+00:00"
},
{
"name": "theseer/tokenizer",
"version": "1.2.2",

View file

@ -4,6 +4,7 @@
@section('main-content')
@if($status)
@include('includes.index')
@else
@include('forms.login')
@endif

View file

@ -0,0 +1,37 @@
<section class="index-header">
<div class="index-header-left">
<h1>Recent</h1>
</div>
<div class="index-header-right"></div>
</section>
<section class="index-recent-pages">
@if($result['entryCount'] != 0)
@foreach($result['pages'] as $page)
@php
$type = '';
$file = '';
if(isset($page['media'][0]['type']))
{
$type = $page['media'][0]['type'];
}
if(isset($page['media'][0]['file']))
{
$file = $page['media'][0]['file'];
}
@endphp
@if($type =='mp4')
<a href="/dashboard/page/edit/{{ $page['uuid'] }}" id="{{ $page['uuid'] }}" class="post-video-link recent-link">
@include('includes.recent-meta')
<video class="post-video" loop muted autoplay>
<source src="{{ $file }}" type="video/mp4">
Sorry, your browser doesn't support embedded videos.
</video>
</a>
@else
<a href="/dashboard/page/edit/{{ $page['uuid'] }}" id="{{ $page['uuid'] }}" class="post-link recent-link" style="background: url({{ $file }}) no-repeat center center / cover #fc6399">
@include('includes.recent-meta')
</a>
@endif
@endforeach
@endif
</section>

View file

@ -0,0 +1,46 @@
@php
if($page['menu'] == 'true')
{
$menu = 'true';
}else{
$menu = 'false';
}
if($page['published'] == 'true')
{
$published = 'true';
}else{
$published = 'false';
}
if($page['featured'] == 'true')
{
$featured = 'true';
}else{
$featured = 'false';
}
@endphp
<aside>
<strong>
{{ $page['updated'] }}
</strong>
<hr/>
<strong>
{{ $page['title'] }}
</strong>
<hr/>
<button data-active="{{ $menu }}">
<i class="ti ti-navigation"></i>
</button>
<button data-active="{{ $published }}">
<i class="ti ti-world"></i>
</button>
<button data-active="{{ $featured }}">
<i class="ti ti-star"></i>
</button>
</aside>