231 lines
7.6 KiB
PHP
231 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Render\BladeRenderer;
|
|
use App\Render\MarkdownRenderer;
|
|
use App\Render\Renderer;
|
|
use App\SiteConfiguration;
|
|
use Illuminate\Contracts\Filesystem\Filesystem;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use League\CommonMark\Environment\Environment;
|
|
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
|
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
|
|
use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
|
|
use League\CommonMark\MarkdownConverter;
|
|
|
|
use function Illuminate\Filesystem\join_paths;
|
|
|
|
// TODO: Load page's frontmatter and replace things like
|
|
// title and layout from it. Also set any default,
|
|
// values for things like layout from the config
|
|
// if they're not present on the page.
|
|
class Page
|
|
{
|
|
public string $filename;
|
|
public string $fileBaseName; // Name of the file with extension removed
|
|
public string $fileExtension;
|
|
public string $directory;
|
|
public string $title;
|
|
public string $slug;
|
|
public bool $isIndex = false;
|
|
public bool $isNestedIndex = false;
|
|
public string $key;
|
|
protected Filesystem $sourceDisk;
|
|
public ?string $layout;
|
|
public string $entityName; // The name of this page. Ex: projects/myproject = myproject
|
|
protected string $content;
|
|
// Collection data
|
|
public ?string $collection = null;
|
|
public array $collectionHierarchy = []; // All collections matched from least to most
|
|
public array $collectionProps = [];
|
|
|
|
public function __construct(public string $path)
|
|
{
|
|
$this->sourceDisk = Storage::disk('source');
|
|
|
|
$this->filename = basename($this->path);
|
|
$this->fileExtension = (str_ends_with($this->filename, '.blade.php')) ? 'blade.php' : pathinfo($this->filename, PATHINFO_EXTENSION);
|
|
$this->fileExtension = str($this->fileExtension)->trim('.')->toString();
|
|
$this->fileBaseName = str($this->filename)->remove("." . $this->fileExtension)->toString();
|
|
$this->title = str($this->filename)->remove("." . $this->fileExtension)->title()->toString();
|
|
$this->slug = str($this->title)->slug();
|
|
$this->directory = dirname($this->path);
|
|
$this->key = str($this->path)->remove("." . $this->fileExtension)->replace("/", ".")->lower()->toString();
|
|
|
|
if ($this->fileBaseName == "index") {
|
|
$this->isIndex = true;
|
|
$this->isNestedIndex = true;
|
|
}
|
|
|
|
$this->entityName = $this->isIndex ? basename($this->directory) : $this->slug;
|
|
|
|
$this->detectCollection();
|
|
$this->fillAdditionalData();
|
|
}
|
|
|
|
public function __get($name)
|
|
{
|
|
if (!empty($this->$name)) {
|
|
return $this->name;
|
|
}
|
|
return $this->collectionProps[$name] ?? null;
|
|
}
|
|
|
|
public function detectCollection()
|
|
{
|
|
$key_parts = explode('.', $this->key);
|
|
$collections = SiteConfiguration::collections();
|
|
// Turn collections into an array containing
|
|
// the parts and the length so we can easily
|
|
// filter likely matches.
|
|
$collections = collect($collections)->map(function ($name) {
|
|
$parts = explode('.', $name);
|
|
return [
|
|
'name' => $name,
|
|
'parts' => $parts,
|
|
'length' => count($parts)
|
|
];
|
|
})
|
|
->filter(fn($col) => $col['length'] <= count($key_parts))
|
|
->sortBy('length');
|
|
|
|
foreach ($collections as $c) {
|
|
$parts = $c['parts'];
|
|
// Get a subset of max length key parts
|
|
$key_compare = array_slice($key_parts, 0, count($parts));
|
|
$props = [];
|
|
$match = true;
|
|
foreach ($key_compare as $i => $v) {
|
|
$check = $parts[$i];
|
|
// Check if it's a parameter like [project_name]
|
|
if (str($check)->isMatch('/\[[a-z_]+\]/')) {
|
|
$key = str($check)->remove(['[', ']'])->toString();
|
|
$props[$key] = $v;
|
|
continue;
|
|
}
|
|
|
|
if ($v !== $check) {
|
|
$match = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($match) {
|
|
$this->collectionHierarchy[] = $c['name'];
|
|
$this->collection = $c['name'];
|
|
$this->collectionProps = $props;
|
|
}
|
|
}
|
|
|
|
// After detecting the collection, we can also
|
|
// determine if the file is an index in that
|
|
// collection. Example:
|
|
// collection = projects
|
|
// file path = projects/myproject.md
|
|
if (str($this->key)->remove($this->collection)->remove(".") == $this->fileBaseName) {
|
|
$this->isIndex = true;
|
|
}
|
|
}
|
|
|
|
public function applyCollectionSettings(string $collection)
|
|
{
|
|
$config = SiteConfiguration::getConfig()['collections'][$collection];
|
|
|
|
$fill = ['layout'];
|
|
|
|
foreach ($fill as $field) {
|
|
$this->$field = $config[$field];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Grab data from the file's front matter
|
|
* as well as the config and overwrite
|
|
* or fill in any missing attributes.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function fillAdditionalData()
|
|
{
|
|
// Apply collection settings in a hierarchical
|
|
// manner, with the settings from the most
|
|
// matched collection taking precedence
|
|
// over the least.
|
|
foreach ($this->collectionHierarchy as $collection) {
|
|
$this->applyCollectionSettings($collection);
|
|
}
|
|
|
|
// After the hierarchy, also apply settings
|
|
// from the detected collection to be
|
|
// safe.
|
|
if (!empty($this->collection)) {
|
|
$this->applyCollectionSettings($collection);
|
|
}
|
|
|
|
// Grab front matter data if any. Apply any settings
|
|
// from it since it takes precedence over
|
|
// collection settings.
|
|
if ($this->fileExtension == 'md') {
|
|
// Build the markdown parser
|
|
$mdEnvironment = new Environment();
|
|
$mdEnvironment->addExtension(new CommonMarkCoreExtension);
|
|
$mdEnvironment->addExtension(new FrontMatterExtension);
|
|
|
|
$parser = new MarkdownConverter($mdEnvironment);
|
|
$parsed = $parser->convert($this->content());
|
|
if ($parsed instanceof RenderedContentWithFrontMatter) {
|
|
$frontMatter = $parsed->getFrontMatter();
|
|
foreach ($frontMatter as $field => $value) {
|
|
$this->$field = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If layout is still empty just set it to main
|
|
if (empty($this->layout)) $this->layout = 'main';
|
|
}
|
|
|
|
public function renderer(): Renderer
|
|
{
|
|
return ($this->fileExtension == 'blade.php') ? new BladeRenderer : new MarkdownRenderer;
|
|
}
|
|
|
|
public function render(): string
|
|
{
|
|
return $this->renderer()->render($this);
|
|
}
|
|
|
|
public function outputPath(): string
|
|
{
|
|
|
|
$baseDirectory = $this->directory;
|
|
if ($this->isIndex) {
|
|
return join_paths($baseDirectory, "index.html");
|
|
}
|
|
|
|
return join_paths($baseDirectory, "{$this->slug}", "index.html");
|
|
}
|
|
|
|
public function url(): string
|
|
{
|
|
$baseDirectory = $this->directory;
|
|
if ($this->isIndex && $this->isNestedIndex) {
|
|
return str($this->directory)->prepend("/");
|
|
}
|
|
|
|
return str(join_paths($baseDirectory, "{$this->slug}"))->prepend("/");
|
|
}
|
|
|
|
public function content(): string
|
|
{
|
|
if (!empty($this->content)) {
|
|
return $this->content;
|
|
}
|
|
|
|
$content = Storage::disk('source')->get($this->path);
|
|
$this->content = $content;
|
|
return $content;
|
|
}
|
|
}
|