Added content-view body classes generated from tags

Included tests to cover.

Closes #3583
This commit is contained in:
Dan Brown 2022-07-23 18:29:04 +01:00
parent 840a1ea011
commit 975ba4f8d8
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
11 changed files with 96 additions and 5 deletions

View File

@ -0,0 +1,50 @@
<?php
namespace BookStack\Actions;
class TagClassGenerator
{
protected array $tags;
/**
* @param Tag[] $tags
*/
public function __construct(array $tags)
{
$this->tags = $tags;
}
/**
* @return string[]
*/
public function generate(): array
{
$classes = [];
foreach ($this->tags as $tag) {
$name = $this->normalizeTagClassString($tag->name);
$value = $this->normalizeTagClassString($tag->value);
$classes[] = 'tag-name-' . $name;
if ($value) {
$classes[] = 'tag-value-' . $value;
$classes[] = 'tag-pair-' . $name . '-' . $value;
}
}
return array_unique($classes);
}
public function generateAsString(): string
{
return implode(' ', $this->generate());
}
protected function normalizeTagClassString(string $value): string
{
$value = str_replace(' ', '', strtolower($value));
$value = str_replace('-', '', strtolower($value));
return $value;
}
}

View File

@ -13,6 +13,8 @@
@endif @endif
@endpush @endpush
@include('entities.body-tag-classes', ['entity' => $book])
@section('body') @section('body')
<div class="mb-s"> <div class="mb-s">

View File

@ -10,6 +10,8 @@
<meta property="og:description" content="{{ Str::limit($chapter->description, 100, '...') }}"> <meta property="og:description" content="{{ Str::limit($chapter->description, 100, '...') }}">
@endpush @endpush
@include('entities.body-tag-classes', ['entity' => $chapter])
@section('body') @section('body')
<div class="mb-m print-hidden"> <div class="mb-m print-hidden">

View File

@ -0,0 +1 @@
@push('body-class', e((new \BookStack\Actions\TagClassGenerator($entity->tags->all()))->generateAsString() . ' '))

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ config('app.lang') }}" <html lang="{{ config('app.lang') }}"
dir="{{ config('app.rtl') ? 'rtl' : 'ltr' }}" dir="{{ config('app.rtl') ? 'rtl' : 'ltr' }}"
class="{{ setting()->getForCurrentUser('dark-mode-enabled') ? 'dark-mode ' : '' }}@yield('body-class')"> class="{{ setting()->getForCurrentUser('dark-mode-enabled') ? 'dark-mode ' : '' }}">
<head> <head>
<title>{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name') }}</title> <title>{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name') }}</title>
@ -31,7 +31,7 @@
<!-- Translations for JS --> <!-- Translations for JS -->
@stack('translations') @stack('translations')
</head> </head>
<body class="@yield('body-class')"> <body class="@stack('body-class')">
@include('layouts.parts.base-body-start') @include('layouts.parts.base-body-start')
@include('common.skip-to-content') @include('common.skip-to-content')

View File

@ -1,6 +1,6 @@
@extends('layouts.base') @extends('layouts.base')
@section('body-class', 'tri-layout') @push('body-class', 'tri-layout ')
@section('content-components', 'tri-layout') @section('content-components', 'tri-layout')
@section('content') @section('content')

View File

@ -1,6 +1,6 @@
@extends('layouts.base') @extends('layouts.base')
@section('body-class', 'flexbox') @push('body-class', 'flexbox ')
@section('content') @section('content')

View File

@ -4,6 +4,8 @@
<meta property="og:description" content="{{ Str::limit($page->text, 100, '...') }}"> <meta property="og:description" content="{{ Str::limit($page->text, 100, '...') }}">
@endpush @endpush
@include('entities.body-tag-classes', ['entity' => $page])
@section('body') @section('body')
<div class="mb-m print-hidden"> <div class="mb-m print-hidden">

View File

@ -7,6 +7,8 @@
@endif @endif
@endpush @endpush
@include('entities.body-tag-classes', ['entity' => $shelf])
@section('body') @section('body')
<div class="mb-s"> <div class="mb-s">

View File

@ -198,9 +198,28 @@ class TagTest extends TestCase
public function test_tag_index_shows_message_on_no_results() public function test_tag_index_shows_message_on_no_results()
{ {
/** @var Page $page */
$resp = $this->asEditor()->get('/tags?search=testingval'); $resp = $this->asEditor()->get('/tags?search=testingval');
$resp->assertSee('No items available'); $resp->assertSee('No items available');
$resp->assertSee('Tags can be assigned via the page editor sidebar'); $resp->assertSee('Tags can be assigned via the page editor sidebar');
} }
public function test_tag_classes_visible_on_entities()
{
$this->asEditor();
foreach ($this->getEachEntityType() as $entity) {
$entity->tags()->create(['name' => 'My Super Tag Name', 'value' => 'An-awesome-value']);
$html = $this->withHtml($this->get($entity->getUrl()));
$html->assertElementExists('body.tag-name-mysupertagname.tag-value-anawesomevalue.tag-pair-mysupertagname-anawesomevalue');
}
}
public function test_tag_classes_are_escaped()
{
$page = Page::query()->first();
$page->tags()->create(['name' => '<>']);
$resp = $this->asEditor()->get($page->getUrl());
$resp->assertDontSee('tag-name-<>', false);
$resp->assertSee('tag-name-&lt;&gt;', false);
}
} }

View File

@ -428,4 +428,17 @@ abstract class TestCase extends BaseTestCase
$this->assertDatabaseHas('activities', $detailsToCheck); $this->assertDatabaseHas('activities', $detailsToCheck);
} }
/**
* @return Entity[]
*/
protected function getEachEntityType(): array
{
return [
'page' => Page::query()->first(),
'chapter' => Chapter::query()->first(),
'book' => Book::query()->first(),
'bookshelf' => Bookshelf::query()->first(),
];
}
} }