342 lines
8.1 KiB
PHP
342 lines
8.1 KiB
PHP
<?php
|
|
|
|
namespace Kirby\Panel\Lab;
|
|
|
|
use Kirby\Cms\App;
|
|
use Kirby\Data\Data;
|
|
use Kirby\Filesystem\Dir;
|
|
use Kirby\Filesystem\F;
|
|
use Kirby\Toolkit\A;
|
|
use Kirby\Toolkit\Str;
|
|
|
|
/**
|
|
* Docs for a single Vue component
|
|
*
|
|
* @internal
|
|
* @since 4.0.0
|
|
* @codeCoverageIgnore
|
|
*
|
|
* @package Kirby Panel
|
|
* @author Nico Hoffmann <nico@getkirby.com>
|
|
* @link https://getkirby.com
|
|
* @copyright Bastian Allgeier
|
|
* @license https://getkirby.com/license
|
|
*/
|
|
class Docs
|
|
{
|
|
protected array $json;
|
|
protected App $kirby;
|
|
|
|
public function __construct(
|
|
protected string $name
|
|
) {
|
|
// protect against path traversal
|
|
$this->name = basename($name);
|
|
$this->kirby = App::instance();
|
|
$this->json = $this->read();
|
|
}
|
|
|
|
public static function all(): array
|
|
{
|
|
$dist = static::root();
|
|
$tmp = static::root(true);
|
|
$files = Dir::inventory($dist)['files'];
|
|
|
|
if (Dir::exists($tmp) === true) {
|
|
$files = [...Dir::inventory($tmp)['files'], ...$files];
|
|
}
|
|
|
|
$docs = A::map(
|
|
$files,
|
|
function ($file) {
|
|
$component = 'k-' . Str::camelToKebab(F::name($file['filename']));
|
|
|
|
return [
|
|
'image' => [
|
|
'icon' => 'book',
|
|
'back' => 'white',
|
|
],
|
|
'text' => $component,
|
|
'link' => '/lab/docs/' . $component,
|
|
];
|
|
}
|
|
);
|
|
|
|
usort($docs, fn ($a, $b) => $a['text'] <=> $b['text']);
|
|
|
|
return array_values($docs);
|
|
}
|
|
|
|
public function deprecated(): string|null
|
|
{
|
|
return $this->kt($this->json['tags']['deprecated'][0]['description'] ?? '');
|
|
}
|
|
|
|
public function description(): string
|
|
{
|
|
return $this->kt($this->json['description'] ?? '');
|
|
}
|
|
|
|
public function docBlock(): string
|
|
{
|
|
return $this->kt($this->json['docsBlocks'][0] ?? '');
|
|
}
|
|
|
|
public function events(): array
|
|
{
|
|
$events = A::map(
|
|
$this->json['events'] ?? [],
|
|
fn ($event) => [
|
|
'name' => $event['name'],
|
|
'description' => $this->kt($event['description'] ?? ''),
|
|
'deprecated' => $this->kt($event['tags']['deprecated'][0]['description'] ?? ''),
|
|
'since' => $event['tags']['since'][0]['description'] ?? null,
|
|
'properties' => A::map(
|
|
$event['properties'] ?? [],
|
|
fn ($property) => [
|
|
'name' => $property['name'],
|
|
'type' => $property['type']['names'][0] ?? '',
|
|
'description' => $this->kt($property['description'] ?? '', true),
|
|
]
|
|
),
|
|
]
|
|
);
|
|
|
|
usort($events, fn ($a, $b) => $a['name'] <=> $b['name']);
|
|
|
|
return $events;
|
|
}
|
|
|
|
public function examples(): array
|
|
{
|
|
if (empty($this->json['tags']['examples']) === false) {
|
|
return $this->json['tags']['examples'];
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
public function file(string $context): string
|
|
{
|
|
$root = match ($context) {
|
|
'dev' => $this->kirby->root('panel') . '/tmp',
|
|
'dist' => $this->kirby->root('panel') . '/dist/ui',
|
|
};
|
|
|
|
$name = Str::after($this->name, 'k-');
|
|
$name = Str::kebabToCamel($name);
|
|
return $root . '/' . $name . '.json';
|
|
}
|
|
|
|
public function github(): string
|
|
{
|
|
return 'https://github.com/getkirby/kirby/tree/main/panel/' . $this->json['sourceFile'];
|
|
}
|
|
|
|
public static function installed(): bool
|
|
{
|
|
return Dir::exists(static::root()) === true;
|
|
}
|
|
|
|
protected function kt(string $text, bool $inline = false): string
|
|
{
|
|
return $this->kirby->kirbytext($text, [
|
|
'markdown' => [
|
|
'breaks' => false,
|
|
'inline' => $inline,
|
|
]
|
|
]);
|
|
}
|
|
|
|
public function lab(): string|null
|
|
{
|
|
$root = $this->kirby->root('panel') . '/lab';
|
|
|
|
foreach (glob($root . '/{,*/,*/*/,*/*/*/}index.php', GLOB_BRACE) as $example) {
|
|
$props = require $example;
|
|
|
|
if (($props['docs'] ?? null) === $this->name) {
|
|
return Str::before(Str::after($example, $root), 'index.php');
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function methods(): array
|
|
{
|
|
$methods = A::map(
|
|
$this->json['methods'] ?? [],
|
|
fn ($method) => [
|
|
'name' => $method['name'],
|
|
'description' => $this->kt($method['description'] ?? ''),
|
|
'deprecated' => $this->kt($method['tags']['deprecated'][0]['description'] ?? ''),
|
|
'since' => $method['tags']['since'][0]['description'] ?? null,
|
|
'params' => A::map(
|
|
$method['params'] ?? [],
|
|
fn ($param) => [
|
|
'name' => $param['name'],
|
|
'type' => $param['type']['name'] ?? '',
|
|
'description' => $this->kt($param['description'] ?? '', true),
|
|
]
|
|
),
|
|
'returns' => $method['returns']['type']['name'] ?? null,
|
|
]
|
|
);
|
|
|
|
usort($methods, fn ($a, $b) => $a['name'] <=> $b['name']);
|
|
|
|
return $methods;
|
|
}
|
|
|
|
public function name(): string
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
public function prop(string|int $key): array|null
|
|
{
|
|
$prop = $this->json['props'][$key];
|
|
|
|
// filter private props
|
|
if (($prop['tags']['access'][0]['description'] ?? null) === 'private') {
|
|
return null;
|
|
}
|
|
|
|
// filter unset props
|
|
if (($type = $prop['type']['name'] ?? null) === 'null') {
|
|
return null;
|
|
}
|
|
|
|
$default = $prop['defaultValue']['value'] ?? null;
|
|
$deprecated = $this->kt($prop['tags']['deprecated'][0]['description'] ?? '');
|
|
|
|
return [
|
|
'name' => Str::camelToKebab($prop['name']),
|
|
'type' => $type,
|
|
'description' => $this->kt($prop['description'] ?? ''),
|
|
'default' => $this->propDefault($default, $type),
|
|
'deprecated' => $deprecated,
|
|
'example' => $prop['tags']['example'][0]['description'] ?? null,
|
|
'required' => $prop['required'] ?? false,
|
|
'since' => $prop['tags']['since'][0]['description'] ?? null,
|
|
'value' => $prop['tags']['value'][0]['description'] ?? null,
|
|
'values' => $prop['values'] ?? null,
|
|
];
|
|
}
|
|
|
|
protected function propDefault(
|
|
string|null $default,
|
|
string|null $type
|
|
): string|null {
|
|
if ($default !== null) {
|
|
// normalize longform function
|
|
if (preg_match('/function\(\) {.*return (.*);.*}/si', $default, $matches) === 1) {
|
|
return $matches[1];
|
|
}
|
|
|
|
// normalize object shorthand function
|
|
if (preg_match('/\(\) => \((.*)\)/si', $default, $matches) === 1) {
|
|
return $matches[1];
|
|
}
|
|
|
|
// normalize all other defaults from shorthand function
|
|
if (preg_match('/\(\) => (.*)/si', $default, $matches) === 1) {
|
|
return $matches[1];
|
|
}
|
|
|
|
return $default;
|
|
}
|
|
|
|
// if type is boolean primarily and no default
|
|
// value has been set, add `false` as default
|
|
// for clarity
|
|
if (Str::startsWith($type, 'boolean')) {
|
|
return 'false';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function props(): array
|
|
{
|
|
$props = A::map(
|
|
array_keys($this->json['props'] ?? []),
|
|
fn ($key) => $this->prop($key)
|
|
);
|
|
|
|
// remove empty props
|
|
$props = array_filter($props);
|
|
|
|
usort($props, fn ($a, $b) => $a['name'] <=> $b['name']);
|
|
|
|
// always return an array
|
|
return array_values($props);
|
|
}
|
|
|
|
protected function read(): array
|
|
{
|
|
$file = $this->file('dev');
|
|
|
|
if (file_exists($file) === false) {
|
|
$file = $this->file('dist');
|
|
}
|
|
|
|
return Data::read($file);
|
|
}
|
|
|
|
public static function root(bool $tmp = false): string
|
|
{
|
|
return App::instance()->root('panel') . '/' . match ($tmp) {
|
|
true => 'tmp',
|
|
default => 'dist/ui',
|
|
};
|
|
}
|
|
|
|
public function since(): string|null
|
|
{
|
|
return $this->json['tags']['since'][0]['description'] ?? null;
|
|
}
|
|
|
|
public function slots(): array
|
|
{
|
|
$slots = A::map(
|
|
$this->json['slots'] ?? [],
|
|
fn ($slot) => [
|
|
'name' => $slot['name'],
|
|
'description' => $this->kt($slot['description'] ?? ''),
|
|
'deprecated' => $this->kt($slot['tags']['deprecated'][0]['description'] ?? ''),
|
|
'since' => $slot['tags']['since'][0]['description'] ?? null,
|
|
'bindings' => A::map(
|
|
$slot['bindings'] ?? [],
|
|
fn ($binding) => [
|
|
'name' => $binding['name'],
|
|
'type' => $binding['type']['name'] ?? '',
|
|
'description' => $this->kt($binding['description'] ?? '', true),
|
|
]
|
|
),
|
|
]
|
|
);
|
|
|
|
usort($slots, fn ($a, $b) => $a['name'] <=> $b['name']);
|
|
|
|
return $slots;
|
|
}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'component' => $this->name(),
|
|
'deprecated' => $this->deprecated(),
|
|
'description' => $this->description(),
|
|
'docBlock' => $this->docBlock(),
|
|
'events' => $this->events(),
|
|
'examples' => $this->examples(),
|
|
'github' => $this->github(),
|
|
'methods' => $this->methods(),
|
|
'props' => $this->props(),
|
|
'since' => $this->since(),
|
|
'slots' => $this->slots(),
|
|
];
|
|
}
|
|
}
|