Included tests to cover.
Closes #3583
--- /dev/null
+<?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;
+ }
+
+}
\ No newline at end of file
@endif
@endpush
+@include('entities.body-tag-classes', ['entity' => $book])
+
@section('body')
<div class="mb-s">
<meta property="og:description" content="{{ Str::limit($chapter->description, 100, '...') }}">
@endpush
+@include('entities.body-tag-classes', ['entity' => $chapter])
+
@section('body')
<div class="mb-m print-hidden">
--- /dev/null
+@push('body-class', e((new \BookStack\Actions\TagClassGenerator($entity->tags->all()))->generateAsString() . ' '))
\ No newline at end of file
<!DOCTYPE html>
<html lang="{{ config('app.lang') }}"
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>
<title>{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name') }}</title>
<!-- Translations for JS -->
@stack('translations')
</head>
-<body class="@yield('body-class')">
+<body class="@stack('body-class')">
@include('layouts.parts.base-body-start')
@include('common.skip-to-content')
@extends('layouts.base')
-@section('body-class', 'tri-layout')
+@push('body-class', 'tri-layout ')
@section('content-components', 'tri-layout')
@section('content')
@extends('layouts.base')
-@section('body-class', 'flexbox')
+@push('body-class', 'flexbox ')
@section('content')
<meta property="og:description" content="{{ Str::limit($page->text, 100, '...') }}">
@endpush
+@include('entities.body-tag-classes', ['entity' => $page])
+
@section('body')
<div class="mb-m print-hidden">
@endif
@endpush
+@include('entities.body-tag-classes', ['entity' => $shelf])
+
@section('body')
<div class="mb-s">
public function test_tag_index_shows_message_on_no_results()
{
- /** @var Page $page */
$resp = $this->asEditor()->get('/tags?search=testingval');
$resp->assertSee('No items available');
$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-<>', false);
+ }
}
$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(),
+ ];
+ }
}