From d795af04dfa69fe1fc95f13aa0b0d43f3bdac268 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Wed, 4 May 2022 21:03:13 +0100 Subject: [PATCH 01/22] Added ability to escape role "External Auth ID" commas - Using a backslash in this field before a comma. - Could potentially (Although unlikely) be a breaking change. For #3405 --- app/Auth/Access/GroupSyncService.php | 18 ++++++-- database/factories/Auth/RoleFactory.php | 1 + tests/Auth/GroupSyncServiceTest.php | 59 +++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 tests/Auth/GroupSyncServiceTest.php diff --git a/app/Auth/Access/GroupSyncService.php b/app/Auth/Access/GroupSyncService.php index db19b007a..74f0539d8 100644 --- a/app/Auth/Access/GroupSyncService.php +++ b/app/Auth/Access/GroupSyncService.php @@ -28,10 +28,8 @@ class GroupSyncService */ protected function externalIdMatchesGroupNames(string $externalId, array $groupNames): bool { - $externalAuthIds = explode(',', strtolower($externalId)); - - foreach ($externalAuthIds as $externalAuthId) { - if (in_array(trim($externalAuthId), $groupNames)) { + foreach ($this->parseRoleExternalAuthId($externalId) as $externalAuthId) { + if (in_array($externalAuthId, $groupNames)) { return true; } } @@ -39,6 +37,18 @@ class GroupSyncService return false; } + protected function parseRoleExternalAuthId(string $externalId): array + { + $inputIds = preg_split('/(?<!\\\),/', $externalId); + $cleanIds = []; + + foreach ($inputIds as $inputId) { + $cleanIds[] = str_replace('\,', ',', trim($inputId)); + } + + return $cleanIds; + } + /** * Match an array of group names to BookStack system roles. * Formats group names to be lower-case and hyphenated. diff --git a/database/factories/Auth/RoleFactory.php b/database/factories/Auth/RoleFactory.php index a952e0487..6dbc1394b 100644 --- a/database/factories/Auth/RoleFactory.php +++ b/database/factories/Auth/RoleFactory.php @@ -23,6 +23,7 @@ class RoleFactory extends Factory return [ 'display_name' => $this->faker->sentence(3), 'description' => $this->faker->sentence(10), + 'external_auth_id' => '', ]; } } diff --git a/tests/Auth/GroupSyncServiceTest.php b/tests/Auth/GroupSyncServiceTest.php new file mode 100644 index 000000000..ee8dee008 --- /dev/null +++ b/tests/Auth/GroupSyncServiceTest.php @@ -0,0 +1,59 @@ +<?php + +namespace Tests\Auth; + +use BookStack\Auth\Access\GroupSyncService; +use BookStack\Auth\Role; +use BookStack\Auth\User; +use Tests\TestCase; + +class GroupSyncServiceTest extends TestCase +{ + + public function test_user_is_assigned_to_matching_roles() + { + $user = $this->getViewer(); + + $roleA = Role::factory()->create(['display_name' => 'Wizards']); + $roleB = Role::factory()->create(['display_name' => 'Gremlins']); + $roleC = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales']); + $roleD = Role::factory()->create(['display_name' => 'DEF456', 'external_auth_id' => 'admin-team']); + + foreach([$roleA, $roleB, $roleC, $roleD] as $role) { + $this->assertFalse($user->hasRole($role->id)); + } + + (new GroupSyncService())->syncUserWithFoundGroups($user, ['Wizards', 'Gremlinz', 'Sales', 'Admin Team'], false); + + $user = User::query()->find($user->id); + $this->assertTrue($user->hasRole($roleA->id)); + $this->assertFalse($user->hasRole($roleB->id)); + $this->assertTrue($user->hasRole($roleC->id)); + $this->assertTrue($user->hasRole($roleD->id)); + } + + public function test_multiple_values_in_role_external_auth_id_handled() + { + $user = $this->getViewer(); + $role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales, engineering, developers, marketers']); + $this->assertFalse($user->hasRole($role->id)); + + (new GroupSyncService())->syncUserWithFoundGroups($user, ['Developers'], false); + + $user = User::query()->find($user->id); + $this->assertTrue($user->hasRole($role->id)); + } + + public function test_commas_can_be_used_in_external_auth_id_if_escaped() + { + $user = $this->getViewer(); + $role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales\,-developers, marketers']); + $this->assertFalse($user->hasRole($role->id)); + + (new GroupSyncService())->syncUserWithFoundGroups($user, ['Sales, Developers'], false); + + $user = User::query()->find($user->id); + $this->assertTrue($user->hasRole($role->id)); + } + +} \ No newline at end of file From bef2045df1c62ca9b2622e924cd8757050acd96f Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Thu, 12 May 2022 17:27:29 +0100 Subject: [PATCH 02/22] Embedded css sources for easier firefox dev work --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b49a2a07f..7a85af47a 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "private": true, "scripts": { - "build:css:dev": "sass ./resources/sass:./public/dist", - "build:css:watch": "sass ./resources/sass:./public/dist --watch", + "build:css:dev": "sass ./resources/sass:./public/dist --embed-sources", + "build:css:watch": "sass ./resources/sass:./public/dist --watch --embed-sources", "build:css:production": "sass ./resources/sass:./public/dist -s compressed", "build:js:dev": "node dev/build/esbuild.js", "build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"", From 221d910ff299fec49f343d3d27f66fa03f3f60aa Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Thu, 12 May 2022 17:27:57 +0100 Subject: [PATCH 03/22] Reduced excess margin in chapter contents lists --- resources/sass/_lists.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 26d12a25d..dfde5a282 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -235,6 +235,7 @@ display: none; padding-inline-start: 0; position: relative; + margin-bottom: 0; } [chapter-toggle].open + .sub-menu { display: block; @@ -485,6 +486,9 @@ ul.pagination { display: block; white-space: nowrap; } + > .entity-list > .entity-list-item:last-child { + margin-bottom: -$-xs; + } } .entity-list-item-image { From a0fe6147d8a658c21271c54110aec2251f4ed08a Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Fri, 13 May 2022 17:12:45 +0100 Subject: [PATCH 04/22] Improved the display of dropdown menus - Tweaked styling to add a little extra shadow and be more rounded to match other UI areas. - Added slight horizontal inset when in right sidebar to prevent shadow being cut-off in most cases. - Added logic to "drop upwards" if dropping down would take the menu offscreen. --- resources/js/components/dropdown.js | 30 ++++++++++++++----- resources/sass/_lists.scss | 10 +++++-- .../views/entities/export-menu.blade.php | 7 ++++- .../pages/parts/editor-toolbar.blade.php | 4 ++- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/resources/js/components/dropdown.js b/resources/js/components/dropdown.js index f761ecf01..e84076502 100644 --- a/resources/js/components/dropdown.js +++ b/resources/js/components/dropdown.js @@ -28,18 +28,31 @@ class DropDown { this.menu.classList.add('anim', 'menuIn'); this.toggle.setAttribute('aria-expanded', 'true'); + const menuOriginalRect = this.menu.getBoundingClientRect(); + let heightOffset = 0; + const toggleHeight = this.toggle.getBoundingClientRect().height; + const dropUpwards = menuOriginalRect.bottom > window.innerHeight; + + // If enabled, Move to body to prevent being trapped within scrollable sections if (this.moveMenu) { - // Move to body to prevent being trapped within scrollable sections - this.rect = this.menu.getBoundingClientRect(); this.body.appendChild(this.menu); this.menu.style.position = 'fixed'; if (this.direction === 'right') { - this.menu.style.right = `${(this.rect.right - this.rect.width)}px`; + this.menu.style.right = `${(menuOriginalRect.right - menuOriginalRect.width)}px`; } else { - this.menu.style.left = `${this.rect.left}px`; + this.menu.style.left = `${menuOriginalRect.left}px`; } - this.menu.style.top = `${this.rect.top}px`; - this.menu.style.width = `${this.rect.width}px`; + this.menu.style.width = `${menuOriginalRect.width}px`; + heightOffset = dropUpwards ? (window.innerHeight - menuOriginalRect.top - toggleHeight / 2) : menuOriginalRect.top; + } + + // Adjust menu to display upwards if near the bottom of the screen + if (dropUpwards) { + this.menu.style.top = 'initial'; + this.menu.style.bottom = `${heightOffset}px`; + } else { + this.menu.style.top = `${heightOffset}px`; + this.menu.style.bottom = 'initial'; } // Set listener to hide on mouse leave or window click @@ -74,13 +87,16 @@ class DropDown { this.menu.style.display = 'none'; this.menu.classList.remove('anim', 'menuIn'); this.toggle.setAttribute('aria-expanded', 'false'); + this.menu.style.top = ''; + this.menu.style.bottom = ''; + if (this.moveMenu) { this.menu.style.position = ''; this.menu.style[this.direction] = ''; - this.menu.style.top = ''; this.menu.style.width = ''; this.container.appendChild(this.menu); } + this.showing = false; } diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index dfde5a282..3d36fd7bd 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -578,8 +578,8 @@ ul.pagination { right: 0; margin: $-m 0; @include lightDark(background-color, #fff, #333); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18); - border-radius: 1px; + box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.18); + border-radius: 3px; min-width: 180px; padding: $-xs 0; @include lightDark(color, #555, #eee); @@ -656,6 +656,12 @@ ul.pagination { } } +// Shift in right-sidebar dropdown menus to prevent shadows +// being cut by scrollable container. +.tri-layout-right .dropdown-menu { + right: $-xs; +} + // Books grid view .featured-image-container { position: relative; diff --git a/resources/views/entities/export-menu.blade.php b/resources/views/entities/export-menu.blade.php index dd7231095..bac240b1e 100644 --- a/resources/views/entities/export-menu.blade.php +++ b/resources/views/entities/export-menu.blade.php @@ -1,13 +1,18 @@ -<div component="dropdown" class="dropdown-container" id="export-menu"> +<div component="dropdown" + class="dropdown-container" + id="export-menu"> + <div refs="dropdown@toggle" class="icon-list-item" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('entities.export') }}" tabindex="0"> <span>@icon('export')</span> <span>{{ trans('entities.export') }}</span> </div> + <ul refs="dropdown@menu" class="wide dropdown-menu" role="menu"> <li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_html') }}</span><span>.html</span></a></li> <li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_pdf') }}</span><span>.pdf</span></a></li> <li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_text') }}</span><span>.txt</span></a></li> <li><a href="{{ $entity->getUrl('/export/markdown') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_md') }}</span><span>.md</span></a></li> </ul> + </div> diff --git a/resources/views/pages/parts/editor-toolbar.blade.php b/resources/views/pages/parts/editor-toolbar.blade.php index 4846f4b76..fa5cb7374 100644 --- a/resources/views/pages/parts/editor-toolbar.blade.php +++ b/resources/views/pages/parts/editor-toolbar.blade.php @@ -65,7 +65,9 @@ </div> <div class="action-buttons px-m py-xs"> - <div component="dropdown" dropdown-move-menu class="dropdown-container"> + <div component="dropdown" + option:dropdown:move-menu="true" + class="dropdown-container"> <button refs="dropdown@toggle" type="button" aria-haspopup="true" aria-expanded="false" class="text-primary text-button">@icon('edit') <span refs="page-editor@changelogDisplay">{{ trans('entities.pages_edit_set_changelog') }}</span></button> <ul refs="dropdown@menu" class="wide dropdown-menu"> <li class="px-l py-m"> From 24b31b624c0f80bc9a9cd3f29fab97fa18f711d1 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Fri, 13 May 2022 18:03:43 +0100 Subject: [PATCH 05/22] Cleaned up entity details listing --- resources/sass/_lists.scss | 16 +++++++ resources/views/books/show.blade.php | 12 +++-- resources/views/chapters/show.blade.php | 22 +++++++-- resources/views/entities/meta.blade.php | 64 +++++++++++++++---------- resources/views/pages/show.blade.php | 37 ++++++++++---- resources/views/shelves/show.blade.php | 12 +++-- 6 files changed, 117 insertions(+), 46 deletions(-) diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 3d36fd7bd..a829e891b 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -729,3 +729,19 @@ ul.pagination { } } } + +.entity-meta-item { + display: flex; + line-height: 1.2; + margin: 0.6em 0; + align-content: start; + gap: $-s; + a { + line-height: 1.2; + } + svg { + flex-shrink: 0; + width: 1em; + margin: 0; + } +} diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index 5263bc810..e0cb4b862 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -67,14 +67,20 @@ @section('right') <div class="mb-xl"> <h5>{{ trans('common.details') }}</h5> - <div class="text-small text-muted blended-links"> + <div class="blended-links"> @include('entities.meta', ['entity' => $book]) @if($book->restricted) <div class="active-restriction"> @if(userCan('restrictions-manage', $book)) - <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a> + <a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.books_permissions_active') }}</div> + </a> @else - @icon('lock'){{ trans('entities.books_permissions_active') }} + <div class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.books_permissions_active') }}</div> + </div> @endif </div> @endif diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index edd39edde..3e015616a 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -64,15 +64,21 @@ <div class="mb-xl"> <h5>{{ trans('common.details') }}</h5> - <div class="blended-links text-small text-muted"> + <div class="blended-links"> @include('entities.meta', ['entity' => $chapter]) @if($book->restricted) <div class="active-restriction"> @if(userCan('restrictions-manage', $book)) - <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a> + <a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.books_permissions_active') }}</div> + </a> @else - @icon('lock'){{ trans('entities.books_permissions_active') }} + <div class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.books_permissions_active') }}</div> + </div> @endif </div> @endif @@ -80,9 +86,15 @@ @if($chapter->restricted) <div class="active-restriction"> @if(userCan('restrictions-manage', $chapter)) - <a href="{{ $chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a> + <a href="{{ $chapter->getUrl('/permissions') }}" class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.chapters_permissions_active') }}</div> + </a> @else - @icon('lock'){{ trans('entities.chapters_permissions_active') }} + <div class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.chapters_permissions_active') }}</div> + </div> @endif </div> @endif diff --git a/resources/views/entities/meta.blade.php b/resources/views/entities/meta.blade.php index 298cc7c3e..83ff23762 100644 --- a/resources/views/entities/meta.blade.php +++ b/resources/views/entities/meta.blade.php @@ -1,50 +1,62 @@ <div class="entity-meta"> @if($entity->isA('revision')) - <div> - @icon('history'){{ trans('entities.pages_revision') }} - {{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }} + <div class="entity-meta-item"> + @icon('history') + <div> + {{ trans('entities.pages_revision') }} + {{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }} + </div> </div> @endif @if ($entity->isA('page')) - <div> - @if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}"> @endif - @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }} - @if (userCan('page-update', $entity))</a>@endif - </div> + @if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}" class="entity-meta-item"> @else <div class="entity-meta-item"> @endif + @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }} + @if (userCan('page-update', $entity))</a> @else </div> @endif @endif @if ($entity->ownedBy && $entity->owned_by !== $entity->created_by) - <div> - @icon('user'){!! trans('entities.meta_owned_name', [ - 'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>" - ]) !!} + <div class="entity-meta-item"> + @icon('user') + <div> + {!! trans('entities.meta_owned_name', [ + 'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>" + ]) !!} + </div> </div> @endif @if ($entity->createdBy) - <div> - @icon('star'){!! trans('entities.meta_created_name', [ - 'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>', - 'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>" - ]) !!} + <div class="entity-meta-item"> + @icon('star') + <div> + {!! trans('entities.meta_created_name', [ + 'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>', + 'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>" + ]) !!} + </div> </div> @else - <div> - @icon('star')<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span> + <div class="entity-meta-item"> + @icon('star') + <span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span> </div> @endif @if ($entity->updatedBy) - <div> - @icon('edit'){!! trans('entities.meta_updated_name', [ - 'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>', - 'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>" - ]) !!} + <div class="entity-meta-item"> + @icon('edit') + <div> + {!! trans('entities.meta_updated_name', [ + 'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>', + 'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>" + ]) !!} + </div> </div> @elseif (!$entity->isA('revision')) - <div> - @icon('edit')<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span> + <div class="entity-meta-item"> + @icon('edit') + <span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span> </div> @endif </div> \ No newline at end of file diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index 0111047c6..2a71c6021 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -76,15 +76,21 @@ @section('right') <div id="page-details" class="entity-details mb-xl"> <h5>{{ trans('common.details') }}</h5> - <div class="body text-small blended-links"> + <div class="blended-links"> @include('entities.meta', ['entity' => $page]) @if($book->restricted) <div class="active-restriction"> @if(userCan('restrictions-manage', $book)) - <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a> + <a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.books_permissions_active') }}</div> + </a> @else - @icon('lock'){{ trans('entities.books_permissions_active') }} + <div class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.books_permissions_active') }}</div> + </div> @endif </div> @endif @@ -92,9 +98,15 @@ @if($page->chapter && $page->chapter->restricted) <div class="active-restriction"> @if(userCan('restrictions-manage', $page->chapter)) - <a href="{{ $page->chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a> + <a href="{{ $page->chapter->getUrl('/permissions') }}" class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.chapters_permissions_active') }}</div> + </a> @else - @icon('lock'){{ trans('entities.chapters_permissions_active') }} + <div class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.chapters_permissions_active') }}</div> + </div> @endif </div> @endif @@ -102,16 +114,23 @@ @if($page->restricted) <div class="active-restriction"> @if(userCan('restrictions-manage', $page)) - <a href="{{ $page->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.pages_permissions_active') }}</a> + <a href="{{ $page->getUrl('/permissions') }}" class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.pages_permissions_active') }}</div> + </a> @else - @icon('lock'){{ trans('entities.pages_permissions_active') }} + <div class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.pages_permissions_active') }}</div> + </div> @endif </div> @endif @if($page->template) - <div> - @icon('template'){{ trans('entities.pages_is_template') }} + <div class="entity-meta-item"> + @icon('template') + <div>{{ trans('entities.pages_is_template') }}</div> </div> @endif </div> diff --git a/resources/views/shelves/show.blade.php b/resources/views/shelves/show.blade.php index 0d592468d..4d440b635 100644 --- a/resources/views/shelves/show.blade.php +++ b/resources/views/shelves/show.blade.php @@ -81,14 +81,20 @@ <div id="details" class="mb-xl"> <h5>{{ trans('common.details') }}</h5> - <div class="text-small text-muted blended-links"> + <div class="blended-links"> @include('entities.meta', ['entity' => $shelf]) @if($shelf->restricted) <div class="active-restriction"> @if(userCan('restrictions-manage', $shelf)) - <a href="{{ $shelf->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.shelves_permissions_active') }}</a> + <a href="{{ $shelf->getUrl('/permissions') }}" class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.shelves_permissions_active') }}</div> + </a> @else - @icon('lock'){{ trans('entities.shelves_permissions_active') }} + <div class="entity-meta-item"> + @icon('lock') + <div>{{ trans('entities.shelves_permissions_active') }}</div> + </div> @endif </div> @endif From 60e319c4b444d84a51f8d914120b6fe4106d7fbe Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Fri, 13 May 2022 18:34:47 +0100 Subject: [PATCH 06/22] Tidied up book navigation styles - Removed background track line since it would darken entity item bars. - Updated item spacing to be a bit tighter. - Updated action hover styles to be a bit lighter, and visible on dark mode, to fit rest of system. --- resources/sass/_lists.scss | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index a829e891b..6204bcc78 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -157,22 +157,6 @@ @include margin($-xs, -$-s, 0, -$-s); padding-inline-start: 0; padding-inline-end: 0; - position: relative; - - &:after, .sub-menu:after { - content: ''; - display: block; - position: absolute; - left: $-m; - top: 1rem; - bottom: 1rem; - border-inline-start: 4px solid rgba(0, 0, 0, 0.1); - z-index: 0; - @include rtl { - left: auto; - right: $-m; - } - } ul { list-style: none; @@ -181,8 +165,8 @@ } .entity-list-item { - padding-top: $-xxs; - padding-bottom: $-xxs; + padding-top: 2px; + padding-bottom: 2px; background-clip: content-box; border-radius: 0 3px 3px 0; padding-inline-end: 0; @@ -193,7 +177,7 @@ } } .entity-list-item.selected { - @include lightDark(background-color, rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.08)); + @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06)); } .entity-list-item.no-hover { margin-top: -$-xs; @@ -220,7 +204,7 @@ align-self: stretch; flex-shrink: 0; border-radius: 1px; - opacity: 0.6; + opacity: 0.8; } .entity-list-item .icon:after { opacity: 1; @@ -440,8 +424,8 @@ ul.pagination { cursor: pointer; } &:not(.no-hover):hover { + @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06)); text-decoration: none; - background-color: rgba(0, 0, 0, 0.1); border-radius: 4px; } &.outline-hover:hover { From e6864a9cff4bb5e6b16caf53720ac12229815422 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 14 May 2022 12:54:23 +0100 Subject: [PATCH 07/22] Improved card list design - Removed border and rounded list item styles to make hover states have less edge detail and to align with other UI elements. - In expanded-detail view, removed space used for entity description if there is not description content existing. --- resources/sass/_blocks.scss | 1 - resources/sass/_lists.scss | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/sass/_blocks.scss b/resources/sass/_blocks.scss index 7d408cd1b..0398224ca 100644 --- a/resources/sass/_blocks.scss +++ b/resources/sass/_blocks.scss @@ -66,7 +66,6 @@ @include lightDark(background-color, #FFF, #222); box-shadow: $bs-card; border-radius: 3px; - border: 1px solid transparent; .body, p.empty-text { padding: $-m; } diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 6204bcc78..7003ae88c 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -400,6 +400,7 @@ ul.pagination { padding: $-s $-m; display: flex; align-items: center; + gap: $-m; background-color: transparent; border: 0; width: 100%; @@ -409,7 +410,6 @@ ul.pagination { color: #666; } > span:first-child { - margin-inline-end: $-m; flex-basis: 1.88em; flex: none; } @@ -449,7 +449,8 @@ ul.pagination { } .card .entity-list-item:not(.no-hover):hover { - @include lightDark(background-color, #F2F2F2, #2d2d2d) + @include lightDark(background-color, #F2F2F2, #2d2d2d); + border-radius: 0; } .card .entity-list-item .entity-list-item:hover { background-color: #EEEEEE; @@ -519,6 +520,9 @@ ul.pagination { font-size: $fs-m * 0.8; padding-top: $-xs; } + .entity-list-item p:empty { + padding-top: 0; + } p { margin: 0; } From 2c74dfd1d4e7f030425221cfc8f7e1f6819c8992 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 14 May 2022 13:11:48 +0100 Subject: [PATCH 08/22] Updated breadcrumb dropdown styles, improved keyboard nav - Removed harsh theme color border between search and content. - Prevented intermediate focus on list container to align arrow & tab behaviour, and to get to content quicker. --- resources/js/components/dropdown.js | 2 +- resources/sass/_components.scss | 5 ++++- resources/views/entities/breadcrumb-listing.blade.php | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/resources/js/components/dropdown.js b/resources/js/components/dropdown.js index e84076502..473db37d4 100644 --- a/resources/js/components/dropdown.js +++ b/resources/js/components/dropdown.js @@ -101,7 +101,7 @@ class DropDown { } getFocusable() { - return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])')); + return Array.from(this.menu.querySelectorAll('[tabindex]:not([tabindex="-1"]),[href],button,input:not([type=hidden])')); } focusNext() { diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index bce456cf2..adb2aabe0 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -767,12 +767,15 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { text-decoration: none; } } - input { + input, input:focus { padding-inline-start: $-xl; border-radius: 0; border: 0; border-bottom: 1px solid #DDD; } + input:focus { + outline: 0; + } } @include smaller-than($m) { diff --git a/resources/views/entities/breadcrumb-listing.blade.php b/resources/views/entities/breadcrumb-listing.blade.php index 929f56ed3..d038de077 100644 --- a/resources/views/entities/breadcrumb-listing.blade.php +++ b/resources/views/entities/breadcrumb-listing.blade.php @@ -18,6 +18,6 @@ <div refs="dropdown-search@loading"> @include('common.loading-icon') </div> - <div refs="dropdown-search@listContainer" class="dropdown-search-list px-m"></div> + <div refs="dropdown-search@listContainer" class="dropdown-search-list px-m" tabindex="-1"></div> </div> </div> \ No newline at end of file From 89dfa43e73be80c571d1929e721d5ba1340a06ba Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 14 May 2022 13:31:24 +0100 Subject: [PATCH 09/22] Fixed loading animation delay Loading animation would show in an unready state due to animation-delay on components. Updated to a negative delay to ensure elements were in correct positions right away upon show. --- resources/sass/styles.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/sass/styles.scss b/resources/sass/styles.scss index 582bf7c75..cd5ab6557 100644 --- a/resources/sass/styles.scss +++ b/resources/sass/styles.scss @@ -79,17 +79,17 @@ $loadingSize: 10px; animation-timing-function: cubic-bezier(.62, .28, .23, .99); margin-inline-end: 4px; background-color: var(--color-page); - animation-delay: 0.3s; + animation-delay: -300ms; } > div:first-child { left: -($loadingSize+$-xs); background-color: var(--color-book); - animation-delay: 0s; + animation-delay: -600ms; } > div:last-of-type { left: $loadingSize+$-xs; background-color: var(--color-chapter); - animation-delay: 0.6s; + animation-delay: 0ms; } > span { margin-inline-start: $-s; From 35a47a273b9364cdcb459eb835fcda355c91df52 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 14 May 2022 13:32:25 +0100 Subject: [PATCH 10/22] Added animation transition for breadcrumb dropdown load Animates the height on breadcrumb dropdown menus to transition to the loaded animations quicker. Includes a new animation helper for doing similar tasks in future. --- resources/js/components/dropdown-search.js | 3 ++ resources/js/services/animations.js | 32 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/resources/js/components/dropdown-search.js b/resources/js/components/dropdown-search.js index e2d55f969..81fa940c2 100644 --- a/resources/js/components/dropdown-search.js +++ b/resources/js/components/dropdown-search.js @@ -1,4 +1,5 @@ import {debounce} from "../services/util"; +import {transitionHeight} from "../services/animations"; class DropdownSearch { @@ -51,7 +52,9 @@ class DropdownSearch { try { const resp = await window.$http.get(this.getAjaxUrl(searchTerm)); + const animate = transitionHeight(this.listContainerElem, 80); this.listContainerElem.innerHTML = resp.data; + animate(); } catch (err) { console.error(err); } diff --git a/resources/js/services/animations.js b/resources/js/services/animations.js index 278a765d5..9ccd5f442 100644 --- a/resources/js/services/animations.js +++ b/resources/js/services/animations.js @@ -82,6 +82,38 @@ export function slideDown(element, animTime = 400) { animateStyles(element, animStyles, animTime); } +/** + * Transition the height of the given element between two states. + * Call with first state, and you'll receive a function in return. + * Call the returned function in the second state to animate between those two states. + * If animating to/from 0-height use the slide-up/slide down as easier alternatives. + * @param {Element} element - Element to animate + * @param {Number} animTime - Animation time in ms + * @returns {function} - Function to run in second state to trigger animation. + */ +export function transitionHeight(element, animTime = 400) { + const startHeight = element.getBoundingClientRect().height; + const initialComputedStyles = getComputedStyle(element); + const startPaddingTop = initialComputedStyles.getPropertyValue('padding-top'); + const startPaddingBottom = initialComputedStyles.getPropertyValue('padding-bottom'); + + return () => { + cleanupExistingElementAnimation(element); + const targetHeight = element.getBoundingClientRect().height; + const computedStyles = getComputedStyle(element); + const targetPaddingTop = computedStyles.getPropertyValue('padding-top'); + const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom'); + const animStyles = { + height: [`${startHeight}px`, `${targetHeight}px`], + overflow: ['hidden', 'hidden'], + paddingTop: [startPaddingTop, targetPaddingTop], + paddingBottom: [startPaddingBottom, targetPaddingBottom], + }; + + animateStyles(element, animStyles, animTime); + }; +} + /** * Animate the css styles of an element using FLIP animation techniques. * Styles must be an object where the keys are style properties, camelcase, and the values From 78920d7d6555fbb1a06b83a9f72ecab64d1cae9d Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 14 May 2022 13:55:03 +0100 Subject: [PATCH 11/22] Updated tri-layout sidebars to not be cut-off by padding Would cause effect where scroll area would be cut of by spacing which looked a bit strange. This retains the same padding sizes but cuts the content at the header or top of viewport. --- resources/sass/_layout.scss | 4 ++-- resources/views/layouts/tri.blade.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss index b1c80cb53..375f36d0f 100644 --- a/resources/sass/_layout.scss +++ b/resources/sass/_layout.scss @@ -295,9 +295,9 @@ body.flexbox { } @include larger-than($xxl) { .tri-layout-left-contents, .tri-layout-right-contents { - padding: $-m; + padding: $-xl $-m; position: sticky; - top: $-m; + top: 0; max-height: 100vh; min-height: 50vh; overflow-y: scroll; diff --git a/resources/views/layouts/tri.blade.php b/resources/views/layouts/tri.blade.php index e95b21445..4571f4471 100644 --- a/resources/views/layouts/tri.blade.php +++ b/resources/views/layouts/tri.blade.php @@ -27,7 +27,7 @@ <div refs="tri-layout@container" class="tri-layout-container" @yield('container-attrs') > - <div class="tri-layout-left print-hidden pt-m" id="sidebar"> + <div class="tri-layout-left print-hidden" id="sidebar"> <aside class="tri-layout-left-contents"> @yield('left') </aside> @@ -39,7 +39,7 @@ </div> </div> - <div class="tri-layout-right print-hidden pt-m"> + <div class="tri-layout-right print-hidden"> <aside class="tri-layout-right-contents"> @yield('right') </aside> From 6fa699a83573aa7bdd49b8b4e346c23b12be83e7 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 14 May 2022 13:59:10 +0100 Subject: [PATCH 12/22] Fixed skip-to-content link shadow being slightly visible Would cause a slight dark area in top left of view while hidden. --- resources/sass/styles.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/sass/styles.scss b/resources/sass/styles.scss index cd5ab6557..ee99d7668 100644 --- a/resources/sass/styles.scss +++ b/resources/sass/styles.scss @@ -138,7 +138,7 @@ $btt-size: 40px; .skip-to-content-link { position: fixed; - top: -$-xxl; + top: -52px; left: 0; background-color: #FFF; z-index: 15; From 9fda0df7981e8fe7e347b4bba5a1b471faaf4be6 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 14 May 2022 14:19:54 +0100 Subject: [PATCH 13/22] Updated dropdown search boxe positions to align with other dropdowns --- resources/sass/_components.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index adb2aabe0..0d21fbc0a 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -739,7 +739,9 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { display: none; position: absolute; z-index: 80; - right: -$-m; + right: 0; + top: 0; + margin-top: $-m; @include rtl { right: auto; left: -$-m; From d20c74babf53f09597a1fcb23e7b9d3bc98666f8 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 14 May 2022 16:05:29 +0100 Subject: [PATCH 14/22] Improved input size consistency Specifically updates dropdown search and user-search implementation, although does affect all inputs. Decouples breadcrum and select-style dropdown search toggles. Addresses #2678 --- resources/sass/_components.scss | 55 ++++++++++++++++-- resources/sass/_forms.scss | 6 +- resources/sass/_header.scss | 25 +------- resources/sass/_layout.scss | 7 +++ .../entities/breadcrumb-listing.blade.php | 2 +- .../views/form/entity-permissions.blade.php | 2 +- resources/views/form/user-select.blade.php | 10 ++-- resources/views/settings/audit.blade.php | 57 +++++++++---------- resources/views/users/delete.blade.php | 2 +- 9 files changed, 99 insertions(+), 67 deletions(-) diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index 0d21fbc0a..e30492b6b 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -731,6 +731,55 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { } } + +.dropdown-search { + position: relative; +} +.dropdown-search-toggle-breadcrumb { + border: 1px solid transparent; + border-radius: 4px; + line-height: normal; + padding: $-xs; + &:hover { + border-color: #DDD; + } + .svg-icon { + margin-inline-end: 0; + } +} +.dropdown-search-toggle-select { + display: flex; + gap: $-s; + line-height: normal; + .svg-icon { + height: 16px; + margin: 0; + } + .avatar { + height: 22px; + width: 22px; + } + .avatar + span { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .dropdown-search-toggle-caret { + font-size: 1.15rem; + } +} +.dropdown-search-toggle-select-label { + min-width: 0; + white-space: nowrap; +} +.dropdown-search-toggle-select-caret { + font-size: 1.5rem; + line-height: 0; + margin-left: auto; + margin-top: -2px; +} + .dropdown-search-dropdown { box-shadow: $bs-med; overflow: hidden; @@ -789,10 +838,4 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { .dropdown-search-dropdown .dropdown-search-list { max-height: 240px; } -} - -.custom-select-input { - max-width: 280px; - border: 1px solid #D4D4D4; - border-radius: 3px; } \ No newline at end of file diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index 665b1213b..f639709fc 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -7,7 +7,8 @@ @include lightDark(color, #666, #AAA); display: inline-block; font-size: $fs-m; - padding: $-xs*1.5; + padding: $-xs*1.8; + height: 40px; width: 250px; max-width: 100%; @@ -373,6 +374,7 @@ input[type=color] { max-width: 840px; margin: 0 auto; border: none; + height: auto; } } @@ -413,9 +415,11 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] { } input { display: block; + padding: $-xs * 1.5; padding-inline-start: $-l + 4px; width: 300px; max-width: 100%; + height: auto; } &.flexible input { width: 100%; diff --git a/resources/sass/_header.scss b/resources/sass/_header.scss index f070f5a18..d5a24a0be 100644 --- a/resources/sass/_header.scss +++ b/resources/sass/_header.scss @@ -86,6 +86,8 @@ header .search-box { border-radius: 40px; color: #EEE; z-index: 2; + height: auto; + padding: $-xs*1.5; padding-inline-start: 40px; &:focus { outline: none; @@ -279,29 +281,6 @@ header .search-box { } } -.dropdown-search { - position: relative; - .dropdown-search-toggle { - padding: $-xs; - border: 1px solid transparent; - border-radius: 4px; - &:hover { - border-color: #DDD; - } - } - .svg-icon { - margin-inline-end: 0; - } -} - -.dropdown-search-toggle.compact { - padding: $-xxs $-xs; - .avatar { - height: 22px; - width: 22px; - } -} - .faded { a, button, span, span > div { color: #666; diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss index 375f36d0f..14a37dd4a 100644 --- a/resources/sass/_layout.scss +++ b/resources/sass/_layout.scss @@ -155,6 +155,13 @@ body.flexbox { } } +.gap-m { + gap: $-m; +} + +.justify-flex-start { + justify-content: flex-start; +} .justify-flex-end { justify-content: flex-end; } diff --git a/resources/views/entities/breadcrumb-listing.blade.php b/resources/views/entities/breadcrumb-listing.blade.php index d038de077..1efe3ba34 100644 --- a/resources/views/entities/breadcrumb-listing.blade.php +++ b/resources/views/entities/breadcrumb-listing.blade.php @@ -2,7 +2,7 @@ option:dropdown-search:url="/search/entity/siblings?entity_type={{$entity->getType()}}&entity_id={{ $entity->id }}" option:dropdown-search:local-search-selector=".entity-list-item" > - <div class="dropdown-search-toggle" refs="dropdown@toggle" + <div class="dropdown-search-toggle-breadcrumb" refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" tabindex="0"> <div class="separator">@icon('chevron-right')</div> </div> diff --git a/resources/views/form/entity-permissions.blade.php b/resources/views/form/entity-permissions.blade.php index ed04bc041..206955fe9 100644 --- a/resources/views/form/entity-permissions.blade.php +++ b/resources/views/form/entity-permissions.blade.php @@ -15,7 +15,7 @@ <div> <div class="form-group"> <label for="owner">{{ trans('entities.permissions_owner') }}</label> - @include('form.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by', 'compact' => false]) + @include('form.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by']) </div> </div> </div> diff --git a/resources/views/form/user-select.blade.php b/resources/views/form/user-select.blade.php index 8823bb075..743795a6d 100644 --- a/resources/views/form/user-select.blade.php +++ b/resources/views/form/user-select.blade.php @@ -1,19 +1,19 @@ -<div class="dropdown-search custom-select-input" components="dropdown dropdown-search user-select" +<div class="dropdown-search" components="dropdown dropdown-search user-select" option:dropdown-search:url="/search/users/select" > <input refs="user-select@input" type="hidden" name="{{ $name }}" value="{{ $user->id ?? '' }}"> <div refs="dropdown@toggle" - class="dropdown-search-toggle {{ $compact ? 'compact' : '' }} flex-container-row items-center" + class="dropdown-search-toggle-select input-base" aria-haspopup="true" aria-expanded="false" tabindex="0"> - <div refs="user-select@user-info" class="flex-container-row items-center px-s"> + <div refs="user-select@user-info" class="dropdown-search-toggle-select-label flex-container-row items-center"> @if($user) - <img class="avatar small mr-m" src="{{ $user->getAvatar($compact ? 22 : 30) }}" alt="{{ $user->name }}"> + <img class="avatar small mr-m" src="{{ $user->getAvatar(30) }}" width="30" height="30" alt="{{ $user->name }}"> <span>{{ $user->name }}</span> @else <span>{{ trans('settings.users_none_selected') }}</span> @endif </div> - <span style="font-size: {{ $compact ? '1.15rem' : '1.5rem' }}; margin-left: auto;"> + <span class="dropdown-search-toggle-select-caret"> @icon('caret-down') </span> </div> diff --git a/resources/views/settings/audit.blade.php b/resources/views/settings/audit.blade.php index 506a735a2..b856d1150 100644 --- a/resources/views/settings/audit.blade.php +++ b/resources/views/settings/audit.blade.php @@ -9,8 +9,9 @@ <h1 class="list-heading">{{ trans('settings.audit') }}</h1> <p class="text-muted">{{ trans('settings.audit_desc') }}</p> - <div class="flex-container-row"> - <div component="dropdown" class="list-sort-type dropdown-container mr-m"> + <form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row wrap justify-flex-start gap-m"> + + <div component="dropdown" class="list-sort-type dropdown-container"> <label for="">{{ trans('settings.audit_event_filter') }}</label> <button refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" class="input-base text-left">{{ $listDetails['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button> <ul refs="dropdown@menu" class="dropdown-menu"> @@ -21,37 +22,35 @@ </ul> </div> - <form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row mr-m"> - @if(!empty($listDetails['event'])) - <input type="hidden" name="event" value="{{ $listDetails['event'] }}"> - @endif + @if(!empty($listDetails['event'])) + <input type="hidden" name="event" value="{{ $listDetails['event'] }}"> + @endif - @foreach(['date_from', 'date_to'] as $filterKey) - <div class="mr-m"> - <label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label> - <input id="audit_filter_{{ $filterKey }}" - component="submit-on-change" - type="date" - name="{{ $filterKey }}" - value="{{ $listDetails[$filterKey] ?? '' }}"> - </div> - @endforeach - - <div class="form-group ml-auto mr-m" - component="submit-on-change" - option:submit-on-change:filter='[name="user"]'> - <label for="owner">{{ trans('settings.audit_table_user') }}</label> - @include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user', 'compact' => true]) + @foreach(['date_from', 'date_to'] as $filterKey) + <div class=> + <label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label> + <input id="audit_filter_{{ $filterKey }}" + component="submit-on-change" + type="date" + name="{{ $filterKey }}" + value="{{ $listDetails[$filterKey] ?? '' }}"> </div> + @endforeach + + <div class="form-group" + component="submit-on-change" + option:submit-on-change:filter='[name="user"]'> + <label for="owner">{{ trans('settings.audit_table_user') }}</label> + @include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user']) + </div> - <div class="form-group ml-auto"> - <label for="ip">{{ trans('settings.audit_table_ip') }}</label> - @include('form.text', ['name' => 'ip', 'model' => (object) $listDetails]) - <input type="submit" style="display: none"> - </div> - </form> - </div> + <div class="form-group"> + <label for="ip">{{ trans('settings.audit_table_ip') }}</label> + @include('form.text', ['name' => 'ip', 'model' => (object) $listDetails]) + <input type="submit" style="display: none"> + </div> + </form> <hr class="mt-l mb-s"> diff --git a/resources/views/users/delete.blade.php b/resources/views/users/delete.blade.php index 9ee5d4c05..b18c182eb 100644 --- a/resources/views/users/delete.blade.php +++ b/resources/views/users/delete.blade.php @@ -19,7 +19,7 @@ <p class="small">{{ trans('settings.users_migrate_ownership_desc') }}</p> </div> <div> - @include('form.user-select', ['name' => 'new_owner_id', 'user' => null, 'compact' => false]) + @include('form.user-select', ['name' => 'new_owner_id', 'user' => null]) </div> </div> @endif From 340c9ec7a12a70d2522a3afcf77964dbf7eddc5f Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Tue, 17 May 2022 13:37:43 +0100 Subject: [PATCH 15/22] Fixed some inputs affected by height changes --- resources/sass/_forms.scss | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index f639709fc..73799f0a0 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -351,16 +351,13 @@ input[type=color] { } } -.inline-input-style { +.title-input input[type="text"] { display: block; width: 100%; padding: $-s; -} - -.title-input input[type="text"] { - @extend .inline-input-style; margin-top: 0; font-size: 2em; + height: auto; } .title-input.page-title { @@ -385,10 +382,12 @@ input[type=color] { } .description-input textarea { - @extend .inline-input-style; + display: block; + width: 100%; + padding: $-s; font-size: $fs-m; color: #666; - width: 100%; + height: auto; } div[editor-type="markdown"] .title-input.page-title input[type="text"] { From 4866a3a1986aaf5b50301d3cc797177be54e64e4 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Tue, 17 May 2022 14:16:43 +0100 Subject: [PATCH 16/22] Refined header bar styles - Updated many items to be flexbox-based. - Updated & aligned hover states across header bar items. --- resources/sass/_header.scss | 62 ++++++++---- resources/views/common/header.blade.php | 124 ++++++++++++------------ 2 files changed, 102 insertions(+), 84 deletions(-) diff --git a/resources/sass/_header.scss b/resources/sass/_header.scss index d5a24a0be..457b96b4b 100644 --- a/resources/sass/_header.scss +++ b/resources/sass/_header.scss @@ -21,19 +21,28 @@ header { color: rgb(250, 250, 250); border-bottom: 1px solid #DDD; box-shadow: $bs-card; - padding: $-xxs 0; @include lightDark(border-bottom-color, #DDD, #000); @include whenDark { filter: saturate(0.8) brightness(0.8); } + .header-links { + display: flex; + align-items: center; + justify-content: end; + } .links { display: inline-block; vertical-align: top; } .links a { display: inline-block; - padding: $-m; + padding: 10px $-m; color: #FFF; + border-radius: 3px; + } + .links a:hover { + text-decoration: none; + background-color: rgba(255, 255, 255, .15); } .dropdown-container { padding-inline-start: $-m; @@ -49,19 +58,25 @@ header { .user-name { vertical-align: top; position: relative; - display: inline-block; + display: inline-flex; + align-items: center; cursor: pointer; - > * { - vertical-align: top; - } + padding: $-s; + margin: 0 (-$-s); + border-radius: 3px; + gap: $-xs; > span { padding-inline-start: $-xs; display: inline-block; - padding-top: $-xxs; + line-height: 1; } > svg { - padding-top: 4px; font-size: 18px; + margin-top: -2px; + margin-inline-end: 0; + } + &:hover { + background-color: rgba(255, 255, 255, 0.15); } @include between($l, $xl) { padding-inline-start: $-xs; @@ -79,7 +94,6 @@ header { header .search-box { display: inline-block; - margin-top: 10px; input { background-color: rgba(0, 0, 0, 0.2); border: 1px solid rgba(255, 255, 255, 0.2); @@ -91,12 +105,15 @@ header .search-box { padding-inline-start: 40px; &:focus { outline: none; - border: 1px solid rgba(255, 255, 255, 0.6); + border: 1px solid rgba(255, 255, 255, 0.4); } } button { z-index: 1; left: 16px; + top: 10px; + color: #FFF; + opacity: 0.6; @include lightDark(color, rgba(255, 255, 255, 0.8), #AAA); @include rtl { left: auto; @@ -106,36 +123,39 @@ header .search-box { margin-block-end: 0; } } - ::-webkit-input-placeholder { /* Chrome/Opera/Safari */ - color: #DDD; - } - ::-moz-placeholder { /* Firefox 19+ */ - color: #DDD; + input::placeholder { + color: #FFF; + opacity: 0.6; } @include between($l, $xl) { max-width: 200px; } + &:focus-within button { + opacity: 1; + } } .logo { - display: inline-block; + display: inline-flex; + padding: ($-s - 6px) $-s; + margin: 6px (-$-s); + gap: $-s; + align-items: center; + border-radius: 4px; &:hover { color: #FFF; text-decoration: none; + background-color: rgba(255, 255, 255, .15); } } + .logo-text { - display: inline-block; font-size: 1.8em; color: #fff; font-weight: 400; - @include padding(14px, $-l, 14px, 0); - vertical-align: top; line-height: 1; } .logo-image { - @include margin($-xs, $-s, $-xs, 0); - vertical-align: top; height: 43px; } diff --git a/resources/views/common/header.blade.php b/resources/views/common/header.blade.php index b5ac520c1..197b80c27 100644 --- a/resources/views/common/header.blade.php +++ b/resources/views/common/header.blade.php @@ -17,7 +17,7 @@ class="mobile-menu-toggle hide-over-l">@icon('more')</button> </div> - <div class="flex-container-row justify-center hide-under-l"> + <div class="flex-container-column items-center justify-center hide-under-l"> @if (hasAppAccess()) <form action="{{ url('/search') }}" method="GET" class="search-box" role="search"> <button id="header-search-box-button" type="submit" aria-label="{{ trans('common.search') }}" tabindex="-1">@icon('search') </button> @@ -28,76 +28,74 @@ @endif </div> - <div class="text-right"> - <nav refs="header-mobile-toggle@menu" class="header-links"> - <div class="links text-center"> - @if (hasAppAccess()) - <a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a> - @if(userCanOnAny('view', \BookStack\Entities\Models\Bookshelf::class) || userCan('bookshelf-view-all') || userCan('bookshelf-view-own')) - <a href="{{ url('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a> - @endif - <a href="{{ url('/books') }}">@icon('books'){{ trans('entities.books') }}</a> - @if(signedInUser() && userCan('settings-manage')) - <a href="{{ url('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a> - @endif - @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage')) - <a href="{{ url('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a> - @endif + <nav refs="header-mobile-toggle@menu" class="header-links"> + <div class="links text-center"> + @if (hasAppAccess()) + <a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a> + @if(userCanOnAny('view', \BookStack\Entities\Models\Bookshelf::class) || userCan('bookshelf-view-all') || userCan('bookshelf-view-own')) + <a href="{{ url('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a> @endif + <a href="{{ url('/books') }}">@icon('books'){{ trans('entities.books') }}</a> + @if(signedInUser() && userCan('settings-manage')) + <a href="{{ url('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a> + @endif + @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage')) + <a href="{{ url('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a> + @endif + @endif - @if(!signedInUser()) - @if(setting('registration-enabled') && config('auth.method') === 'standard') - <a href="{{ url('/register') }}">@icon('new-user'){{ trans('auth.sign_up') }}</a> - @endif - <a href="{{ url('/login') }}">@icon('login'){{ trans('auth.log_in') }}</a> + @if(!signedInUser()) + @if(setting('registration-enabled') && config('auth.method') === 'standard') + <a href="{{ url('/register') }}">@icon('new-user'){{ trans('auth.sign_up') }}</a> @endif - </div> - @if(signedInUser()) - <?php $currentUser = user(); ?> - <div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true"> + <a href="{{ url('/login') }}">@icon('login'){{ trans('auth.log_in') }}</a> + @endif + </div> + @if(signedInUser()) + <?php $currentUser = user(); ?> + <div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true"> <span class="user-name py-s hide-under-l" refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0"> <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}"> <span class="name">{{ $currentUser->getShortName(9) }}</span> @icon('caret-down') </span> - <ul refs="dropdown@menu" class="dropdown-menu" role="menu"> - <li> - <a href="{{ url('/favourites') }}" class="icon-item"> - @icon('star') - <div>{{ trans('entities.my_favourites') }}</div> - </a> - </li> - <li> - <a href="{{ $currentUser->getProfileUrl() }}" class="icon-item"> - @icon('user') - <div>{{ trans('common.view_profile') }}</div> - </a> - </li> - <li> - <a href="{{ $currentUser->getEditUrl() }}" class="icon-item"> - @icon('edit') - <div>{{ trans('common.edit_profile') }}</div> - </a> - </li> - <li> - <form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}" - method="post"> - {{ csrf_field() }} - <button class="icon-item"> - @icon('logout') - <div>{{ trans('auth.logout') }}</div> - </button> - </form> - </li> - <li><hr></li> - <li> - @include('common.dark-mode-toggle', ['classes' => 'icon-item']) - </li> - </ul> - </div> - @endif - </nav> - </div> + <ul refs="dropdown@menu" class="dropdown-menu" role="menu"> + <li> + <a href="{{ url('/favourites') }}" class="icon-item"> + @icon('star') + <div>{{ trans('entities.my_favourites') }}</div> + </a> + </li> + <li> + <a href="{{ $currentUser->getProfileUrl() }}" class="icon-item"> + @icon('user') + <div>{{ trans('common.view_profile') }}</div> + </a> + </li> + <li> + <a href="{{ $currentUser->getEditUrl() }}" class="icon-item"> + @icon('edit') + <div>{{ trans('common.edit_profile') }}</div> + </a> + </li> + <li> + <form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}" + method="post"> + {{ csrf_field() }} + <button class="icon-item"> + @icon('logout') + <div>{{ trans('auth.logout') }}</div> + </button> + </form> + </li> + <li><hr></li> + <li> + @include('common.dark-mode-toggle', ['classes' => 'icon-item']) + </li> + </ul> + </div> + @endif + </nav> </div> </header> From cb1c2db28271d5dafe02e671b1f0c98f4fadf107 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Tue, 17 May 2022 14:27:58 +0100 Subject: [PATCH 17/22] Aligned collapsed header dropdown item styles Previously the desktop-visible items would style different when collapsed into the expanded dropdown menu, compared to existing items. --- resources/sass/_header.scss | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/resources/sass/_header.scss b/resources/sass/_header.scss index 457b96b4b..923f026c2 100644 --- a/resources/sass/_header.scss +++ b/resources/sass/_header.scss @@ -194,23 +194,29 @@ header .search-box { overflow: hidden; position: absolute; box-shadow: $bs-hover; - margin-top: -$-xs; + margin-top: $-m; + padding: $-xs 0; &.show { display: block; } } header .links a, header .dropdown-container ul li a, header .dropdown-container ul li button { text-align: start; - display: block; - padding: $-s $-m; + display: grid; + align-items: center; + padding: 8px $-m; + gap: $-m; color: $text-dark; + grid-template-columns: 16px auto; + line-height: 1.4; @include lightDark(color, $text-dark, #eee); svg { margin-inline-end: $-s; + width: 16px; } &:hover { - @include lightDark(background-color, #eee, #333); - @include lightDark(color, #000, #fff); + background-color: var(--color-primary-light); + color: var(--color-primary); text-decoration: none; } &:focus { From 4759fa1e1fe5f144a7eadeea5b30f554595bbc34 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Tue, 17 May 2022 17:39:31 +0100 Subject: [PATCH 18/22] Made the "Custom HTML Head Content" setting a highlighted code editor --- resources/js/code.mjs | 15 +++++++++++++++ resources/js/components/code-textarea.js | 16 ++++++++++++++++ resources/js/components/index.js | 2 ++ resources/views/settings/customization.blade.php | 8 +++++++- 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 resources/js/components/code-textarea.js diff --git a/resources/js/code.mjs b/resources/js/code.mjs index 8e2ed72c8..537b0d108 100644 --- a/resources/js/code.mjs +++ b/resources/js/code.mjs @@ -242,6 +242,21 @@ export function popupEditor(elem, modeSuggestion) { }); } +/** + * Create an inline editor to replace the given textarea. + * @param {HTMLTextAreaElement} textArea + * @param {String} mode + * @returns {CodeMirror3} + */ +export function inlineEditor(textArea, mode) { + return CodeMirror.fromTextArea(textArea, { + mode: getMode(mode, textArea.value), + lineNumbers: true, + lineWrapping: false, + theme: getTheme(), + }); +} + /** * Set the mode of a codemirror instance. * @param cmInstance diff --git a/resources/js/components/code-textarea.js b/resources/js/components/code-textarea.js new file mode 100644 index 000000000..988e51f19 --- /dev/null +++ b/resources/js/components/code-textarea.js @@ -0,0 +1,16 @@ +/** + * A simple component to render a code editor within the textarea + * this exists upon. + * @extends {Component} + */ +class CodeTextarea { + + async setup() { + const mode = this.$opts.mode; + const Code = await window.importVersioned('code'); + Code.inlineEditor(this.$el, mode); + } + +} + +export default CodeTextarea; \ No newline at end of file diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 6a4a8c2b0..1bbca864c 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -9,6 +9,7 @@ import bookSort from "./book-sort.js" import chapterToggle from "./chapter-toggle.js" import codeEditor from "./code-editor.js" import codeHighlighter from "./code-highlighter.js" +import codeTextarea from "./code-textarea.js" import collapsible from "./collapsible.js" import confirmDialog from "./confirm-dialog" import customCheckbox from "./custom-checkbox.js" @@ -65,6 +66,7 @@ const componentMapping = { "chapter-toggle": chapterToggle, "code-editor": codeEditor, "code-highlighter": codeHighlighter, + "code-textarea": codeTextarea, "collapsible": collapsible, "confirm-dialog": confirmDialog, "custom-checkbox": customCheckbox, diff --git a/resources/views/settings/customization.blade.php b/resources/views/settings/customization.blade.php index b7be95b4a..a7392196b 100644 --- a/resources/views/settings/customization.blade.php +++ b/resources/views/settings/customization.blade.php @@ -119,7 +119,13 @@ <div> <label for="setting-app-custom-head" class="setting-list-label">{{ trans('settings.app_custom_html') }}</label> <p class="small">{{ trans('settings.app_custom_html_desc') }}</p> - <textarea name="setting-app-custom-head" id="setting-app-custom-head" class="simple-code-input mt-m">{{ setting('app-custom-head', '') }}</textarea> + <div class="mt-m"> + <textarea component="code-textarea" + option:code-textarea:mode="html" + name="setting-app-custom-head" + id="setting-app-custom-head" + class="simple-code-input">{{ setting('app-custom-head', '') }}</textarea> + </div> <p class="small text-right">{{ trans('settings.app_custom_html_disabled_notice') }}</p> </div> From b030c1398bc909ebceec092907134da99b439695 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Wed, 18 May 2022 13:18:21 +0100 Subject: [PATCH 19/22] Tweaked chapter list item styles - Improves animation smoothness - Changed animation slideup/down animations to use max-height instead of height to better avoid jutter at the end. - Cleaned spacing to match page items in books listing. --- resources/js/components/chapter-toggle.js | 4 ++-- resources/js/services/animations.js | 4 ++-- resources/sass/_lists.scss | 3 +-- resources/views/chapters/parts/list-item.blade.php | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/resources/js/components/chapter-toggle.js b/resources/js/components/chapter-toggle.js index bfd0ac729..2fb3f3f1e 100644 --- a/resources/js/components/chapter-toggle.js +++ b/resources/js/components/chapter-toggle.js @@ -12,14 +12,14 @@ class ChapterToggle { const list = this.elem.parentNode.querySelector('.inset-list'); this.elem.classList.add('open'); this.elem.setAttribute('aria-expanded', 'true'); - slideDown(list, 240); + slideDown(list, 180); } close() { const list = this.elem.parentNode.querySelector('.inset-list'); this.elem.classList.remove('open'); this.elem.setAttribute('aria-expanded', 'false'); - slideUp(list, 240); + slideUp(list, 180); } click(event) { diff --git a/resources/js/services/animations.js b/resources/js/services/animations.js index 9ccd5f442..12b8077cf 100644 --- a/resources/js/services/animations.js +++ b/resources/js/services/animations.js @@ -49,7 +49,7 @@ export function slideUp(element, animTime = 400) { const currentPaddingTop = computedStyles.getPropertyValue('padding-top'); const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom'); const animStyles = { - height: [`${currentHeight}px`, '0px'], + maxHeight: [`${currentHeight}px`, '0px'], overflow: ['hidden', 'hidden'], paddingTop: [currentPaddingTop, '0px'], paddingBottom: [currentPaddingBottom, '0px'], @@ -73,7 +73,7 @@ export function slideDown(element, animTime = 400) { const targetPaddingTop = computedStyles.getPropertyValue('padding-top'); const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom'); const animStyles = { - height: ['0px', `${targetHeight}px`], + maxHeight: ['0px', `${targetHeight}px`], overflow: ['hidden', 'hidden'], paddingTop: ['0px', targetPaddingTop], paddingBottom: ['0px', targetPaddingBottom], diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 7003ae88c..3569ed3db 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -460,8 +460,7 @@ ul.pagination { padding: $-m; > div { overflow: hidden; - padding: $-xs 0; - margin-top: -$-xs; + padding: 0 0 $-xs 0; } .entity-chip { text-overflow: ellipsis; diff --git a/resources/views/chapters/parts/list-item.blade.php b/resources/views/chapters/parts/list-item.blade.php index 285e34893..5aa9864b2 100644 --- a/resources/views/chapters/parts/list-item.blade.php +++ b/resources/views/chapters/parts/list-item.blade.php @@ -5,7 +5,7 @@ <div class="content"> <h4 class="entity-list-item-name break-text">{{ $chapter->name }}</h4> <div class="entity-item-snippet"> - <p class="text-muted break-text mb-s">{{ $chapter->getExcerpt() }}</p> + <p class="text-muted break-text">{{ $chapter->getExcerpt() }}</p> </div> </div> </a> From eeccc2ef1090d4e0bffa4fda901aede322b0b671 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Wed, 18 May 2022 13:28:34 +0100 Subject: [PATCH 20/22] Readjusted book child item styles after other changes Was extra space showing due to structure changes and flex gap. --- resources/sass/_lists.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 3569ed3db..4521e7fd4 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -6,7 +6,7 @@ justify-self: stretch; align-self: stretch; height: auto; - margin-inline-end: $-l; + margin-inline-end: $-xs; } .icon:after { opacity: 0.5; @@ -58,7 +58,7 @@ } .chapter-expansion-toggle { border-radius: 0 4px 4px 0; - padding: $-xs $-m; + padding: $-xs ($-m + $-xxs); width: 100%; text-align: start; } @@ -448,7 +448,7 @@ ul.pagination { } } -.card .entity-list-item:not(.no-hover):hover { +.card .entity-list-item:not(.no-hover, .book-contents .entity-list-item):hover { @include lightDark(background-color, #F2F2F2, #2d2d2d); border-radius: 0; } From cb10ad804f046d652950dbafb3d549875b4f898f Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Wed, 18 May 2022 14:06:40 +0100 Subject: [PATCH 21/22] Made chapter toggle in book sidebar nav more consistent - Now has a hover state to match other items. - Now spans the full sidebar with like other items. - Also updated chapter-toggle to a chapter-contents component, following the newer component system. --- resources/js/components/chapter-contents.js | 37 +++++++++++++++++++ resources/js/components/chapter-toggle.js | 33 ----------------- resources/js/components/index.js | 4 +- resources/sass/_components.scss | 4 +- resources/sass/_lists.scss | 37 +++++++++++-------- .../views/chapters/parts/child-menu.blade.php | 13 +++++-- .../views/chapters/parts/list-item.blade.php | 9 +++-- 7 files changed, 76 insertions(+), 61 deletions(-) create mode 100644 resources/js/components/chapter-contents.js delete mode 100644 resources/js/components/chapter-toggle.js diff --git a/resources/js/components/chapter-contents.js b/resources/js/components/chapter-contents.js new file mode 100644 index 000000000..c824d0f78 --- /dev/null +++ b/resources/js/components/chapter-contents.js @@ -0,0 +1,37 @@ +import {slideUp, slideDown} from "../services/animations"; + +/** + * @extends {Component} + */ +class ChapterContents { + + setup() { + this.list = this.$refs.list; + this.toggle = this.$refs.toggle; + + this.isOpen = this.toggle.classList.contains('open'); + this.toggle.addEventListener('click', this.click.bind(this)); + } + + open() { + this.toggle.classList.add('open'); + this.toggle.setAttribute('aria-expanded', 'true'); + slideDown(this.list, 180); + this.isOpen = true; + } + + close() { + this.toggle.classList.remove('open'); + this.toggle.setAttribute('aria-expanded', 'false'); + slideUp(this.list, 180); + this.isOpen = false; + } + + click(event) { + event.preventDefault(); + this.isOpen ? this.close() : this.open(); + } + +} + +export default ChapterContents; diff --git a/resources/js/components/chapter-toggle.js b/resources/js/components/chapter-toggle.js deleted file mode 100644 index 2fb3f3f1e..000000000 --- a/resources/js/components/chapter-toggle.js +++ /dev/null @@ -1,33 +0,0 @@ -import {slideUp, slideDown} from "../services/animations"; - -class ChapterToggle { - - constructor(elem) { - this.elem = elem; - this.isOpen = elem.classList.contains('open'); - elem.addEventListener('click', this.click.bind(this)); - } - - open() { - const list = this.elem.parentNode.querySelector('.inset-list'); - this.elem.classList.add('open'); - this.elem.setAttribute('aria-expanded', 'true'); - slideDown(list, 180); - } - - close() { - const list = this.elem.parentNode.querySelector('.inset-list'); - this.elem.classList.remove('open'); - this.elem.setAttribute('aria-expanded', 'false'); - slideUp(list, 180); - } - - click(event) { - event.preventDefault(); - this.isOpen ? this.close() : this.open(); - this.isOpen = !this.isOpen; - } - -} - -export default ChapterToggle; diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 1bbca864c..f360e2b0c 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -6,7 +6,7 @@ import attachmentsList from "./attachments-list.js" import autoSuggest from "./auto-suggest.js" import backToTop from "./back-to-top.js" import bookSort from "./book-sort.js" -import chapterToggle from "./chapter-toggle.js" +import chapterContents from "./chapter-contents.js" import codeEditor from "./code-editor.js" import codeHighlighter from "./code-highlighter.js" import codeTextarea from "./code-textarea.js" @@ -63,7 +63,7 @@ const componentMapping = { "auto-suggest": autoSuggest, "back-to-top": backToTop, "book-sort": bookSort, - "chapter-toggle": chapterToggle, + "chapter-contents": chapterContents, "code-editor": codeEditor, "code-highlighter": codeHighlighter, "code-textarea": codeTextarea, diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index e30492b6b..e3c9d5eea 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -61,7 +61,7 @@ } } -[chapter-toggle] { +.chapter-contents-toggle { cursor: pointer; margin: 0; transition: all ease-in-out 180ms; @@ -77,7 +77,7 @@ transform: rotate(90deg); } svg[data-icon="caret-right"] + * { - margin-inline-start: $-xs; + margin-inline-start: $-xxs; } } diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 4521e7fd4..8ca811c5c 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -56,13 +56,13 @@ > .content { flex: 1; } - .chapter-expansion-toggle { + .chapter-contents-toggle { border-radius: 0 4px 4px 0; padding: $-xs ($-m + $-xxs); width: 100%; text-align: start; } - .chapter-expansion-toggle:hover { + .chapter-contents-toggle:hover { background-color: rgba(0, 0, 0, 0.06); } } @@ -171,6 +171,7 @@ border-radius: 0 3px 3px 0; padding-inline-end: 0; .content { + width: 100%; padding-top: $-xs; padding-bottom: $-xs; max-width: calc(100% - 20px); @@ -193,9 +194,18 @@ margin-top: -.2rem; margin-inline-start: -1rem; } - [chapter-toggle] { - padding-inline-start: .7rem; - padding-bottom: .2rem; + .chapter-contents-toggle { + display: block; + width: 100%; + text-align: left; + padding: $-xxs $-s ($-xxs * 2) $-s; + border-radius: 0 3px 3px 0; + line-height: 1; + margin-top: -$-xxs; + margin-bottom: -$-xxs; + &:hover { + @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06)); + } } .entity-list-item .icon { z-index: 2; @@ -214,16 +224,11 @@ } } -.chapter-child-menu { - ul.sub-menu { - display: none; - padding-inline-start: 0; - position: relative; - margin-bottom: 0; - } - [chapter-toggle].open + .sub-menu { - display: block; - } +.chapter-child-menu ul.sub-menu { + display: none; + padding-inline-start: 0; + position: relative; + margin-bottom: 0; } // Sortable Lists @@ -457,7 +462,7 @@ ul.pagination { } .entity-list-item-children { - padding: $-m; + padding: $-m $-l; > div { overflow: hidden; padding: 0 0 $-xs 0; diff --git a/resources/views/chapters/parts/child-menu.blade.php b/resources/views/chapters/parts/child-menu.blade.php index a00f0f7e1..8fdd09143 100644 --- a/resources/views/chapters/parts/child-menu.blade.php +++ b/resources/views/chapters/parts/child-menu.blade.php @@ -1,9 +1,14 @@ -<div class="chapter-child-menu"> - <button chapter-toggle type="button" aria-expanded="{{ $isOpen ? 'true' : 'false' }}" - class="text-muted @if($isOpen) open @endif"> +<div component="chapter-contents" class="chapter-child-menu"> + <button type="button" + refs="chapter-contents@toggle" + aria-expanded="{{ $isOpen ? 'true' : 'false' }}" + class="text-muted chapter-contents-toggle @if($isOpen) open @endif"> @icon('caret-right') @icon('page') <span>{{ trans_choice('entities.x_pages', $bookChild->visible_pages->count()) }}</span> </button> - <ul class="sub-menu inset-list @if($isOpen) open @endif" @if($isOpen) style="display: block;" @endif role="menu"> + <ul refs="chapter-contents@list" + class="chapter-contents-list sub-menu inset-list @if($isOpen) open @endif" @if($isOpen) + style="display: block;" @endif + role="menu"> @foreach($bookChild->visible_pages as $childPage) <li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}" role="presentation"> @include('entities.list-item-basic', ['entity' => $childPage, 'classes' => $current->matches($childPage)? 'selected' : '' ]) diff --git a/resources/views/chapters/parts/list-item.blade.php b/resources/views/chapters/parts/list-item.blade.php index 5aa9864b2..c3e735e2b 100644 --- a/resources/views/chapters/parts/list-item.blade.php +++ b/resources/views/chapters/parts/list-item.blade.php @@ -12,11 +12,12 @@ @if ($chapter->visible_pages->count() > 0) <div class="chapter chapter-expansion"> <span class="icon text-chapter">@icon('page')</span> - <div class="content"> - <button type="button" chapter-toggle + <div component="chapter-contents" class="content"> + <button type="button" + refs="chapter-contents@toggle" aria-expanded="false" - class="text-muted chapter-expansion-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button> - <div class="inset-list"> + class="text-muted chapter-contents-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button> + <div refs="chapter-contents@list" class="inset-list chapter-contents-list"> <div class="entity-list-item-children"> @include('entities.list', ['entities' => $chapter->visible_pages]) </div> From 2b817e7d2476677a294604e8b7cfbd3d67bc420c Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Thu, 19 May 2022 17:38:04 +0100 Subject: [PATCH 22/22] Updated attachment links to have dropdown for open type - Allows easier accessibility of inline attachments. - Introduces a new split-icon-list-item thingy to support such cases where only part of the button is actually linked. --- resources/icons/download.svg | 1 + resources/lang/en/common.php | 2 + resources/sass/_lists.scss | 60 +++++++++++++++++++++- resources/sass/_pages.scss | 6 ++- resources/views/attachments/list.blade.php | 25 +++++++-- 5 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 resources/icons/download.svg diff --git a/resources/icons/download.svg b/resources/icons/download.svg new file mode 100644 index 000000000..6299571d6 --- /dev/null +++ b/resources/icons/download.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.59 9H15V4c0-.55-.45-1-1-1h-4c-.55 0-1 .45-1 1v5H7.41c-.89 0-1.34 1.08-.71 1.71l4.59 4.59c.39.39 1.02.39 1.41 0l4.59-4.59c.63-.63.19-1.71-.7-1.71zM5 19c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H6c-.55 0-1 .45-1 1z"/></svg> \ No newline at end of file diff --git a/resources/lang/en/common.php b/resources/lang/en/common.php index 2f09e53d1..703a70c7e 100644 --- a/resources/lang/en/common.php +++ b/resources/lang/en/common.php @@ -47,6 +47,8 @@ return [ 'previous' => 'Previous', 'filter_active' => 'Active Filter:', 'filter_clear' => 'Clear Filter', + 'download' => 'Download', + 'open_in_tab' => 'Open in Tab', // Sort Options 'sort_options' => 'Sort Options', diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index 8ca811c5c..19060fbbf 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -453,6 +453,61 @@ ul.pagination { } } +.split-icon-list-item { + display: flex; + align-items: center; + gap: $-m; + background-color: transparent; + border: 0; + width: 100%; + position: relative; + word-break: break-word; + border-radius: 4px; + > a { + padding: $-s $-m; + display: flex; + align-items: center; + gap: $-m; + flex: 1; + } + > a:hover { + text-decoration: none; + } + .icon { + flex-basis: 1.88em; + flex: none; + } + &:hover { + @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06)); + } +} + +.icon-list-item-dropdown { + margin-inline-start: auto; + align-self: stretch; + display: flex; + align-items: stretch; + border-inline-start: 1px solid rgba(0, 0, 0, .1); + visibility: hidden; +} +.split-icon-list-item:hover .icon-list-item-dropdown, +.split-icon-list-item:focus-within .icon-list-item-dropdown { + visibility: visible; +} +.icon-list-item-dropdown-toggle { + padding: $-xs; + display: flex; + align-items: center; + cursor: pointer; + @include lightDark(color, #888, #999); + svg { + margin: 0; + } + &:hover { + @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06)); + } +} + .card .entity-list-item:not(.no-hover, .book-contents .entity-list-item):hover { @include lightDark(background-color, #F2F2F2, #2d2d2d); border-radius: 0; @@ -648,9 +703,10 @@ ul.pagination { } } -// Shift in right-sidebar dropdown menus to prevent shadows +// Shift in sidebar dropdown menus to prevent shadows // being cut by scrollable container. -.tri-layout-right .dropdown-menu { +.tri-layout-right .dropdown-menu, +.tri-layout-left .dropdown-menu { right: $-xs; } diff --git a/resources/sass/_pages.scss b/resources/sass/_pages.scss index 73819975f..3ceec61d0 100755 --- a/resources/sass/_pages.scss +++ b/resources/sass/_pages.scss @@ -396,10 +396,14 @@ body.tox-fullscreen, body.markdown-fullscreen { } } -.entity-list-item > span:first-child, .icon-list-item > span:first-child, .chapter-expansion > .icon { +.entity-list-item > span:first-child, +.icon-list-item > span:first-child, +.split-icon-list-item > a > .icon, +.chapter-expansion > .icon { font-size: 0.8rem; width: 1.88em; height: 1.88em; + flex-shrink: 0; display: flex; align-items: center; justify-content: center; diff --git a/resources/views/attachments/list.blade.php b/resources/views/attachments/list.blade.php index f0a1354ea..a6ffb709b 100644 --- a/resources/views/attachments/list.blade.php +++ b/resources/views/attachments/list.blade.php @@ -1,10 +1,27 @@ <div component="attachments-list"> @foreach($attachments as $attachment) <div class="attachment icon-list"> - <a class="icon-list-item py-xs attachment-{{ $attachment->external ? 'link' : 'file' }}" href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif> - <span class="icon">@icon($attachment->external ? 'export' : 'file')</span> - <span>{{ $attachment->name }}</span> - </a> + <div class="split-icon-list-item attachment-{{ $attachment->external ? 'link' : 'file' }}"> + <a href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif> + <div class="icon">@icon($attachment->external ? 'export' : 'file')</div> + <div class="label">{{ $attachment->name }}</div> + </a> + @if(!$attachment->external) + <div component="dropdown" class="icon-list-item-dropdown"> + <button refs="dropdown@toggle" type="button" class="icon-list-item-dropdown-toggle">@icon('caret-down')</button> + <ul refs="dropdown@menu" class="dropdown-menu" role="menu"> + <a href="{{ $attachment->getUrl(false) }}" class="icon-item"> + @icon('download') + <div>{{ trans('common.download') }}</div> + </a> + <a href="{{ $attachment->getUrl(true) }}" target="_blank" class="icon-item"> + @icon('export') + <div>{{ trans('common.open_in_tab') }}</div> + </a> + </ul> + </div> + @endif + </div> </div> @endforeach </div> \ No newline at end of file