mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-06 09:09:38 +03:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
926abbe776 | ||
|
|
4fabef3a57 | ||
|
|
65ebffa002 | ||
|
|
a04064f981 | ||
|
|
7d19057e68 | ||
|
|
0de0507137 | ||
|
|
7a8954ee65 | ||
|
|
5ef4cd80c3 | ||
|
|
e01f23583f | ||
|
|
b1ee1a856f | ||
|
|
4da72aa267 | ||
|
|
3dda622f0a | ||
|
|
7d951b842c | ||
|
|
94bf5b8fbb |
12
.github/workflows/phpunit.yml
vendored
12
.github/workflows/phpunit.yml
vendored
@@ -2,15 +2,11 @@ name: phpunit
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- release
|
||||
- gh_actions_update
|
||||
branches-ignore:
|
||||
- l10n_master
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
- '*/*'
|
||||
- '!l10n_master'
|
||||
branches-ignore:
|
||||
- l10n_master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
12
.github/workflows/test-migrations.yml
vendored
12
.github/workflows/test-migrations.yml
vendored
@@ -2,15 +2,11 @@ name: test-migrations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- release
|
||||
- gh_actions_update
|
||||
branches-ignore:
|
||||
- l10n_master
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
- '*/*'
|
||||
- '!l10n_master'
|
||||
branches-ignore:
|
||||
- l10n_master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -45,7 +45,7 @@ class ThemeService
|
||||
public function readThemeActions()
|
||||
{
|
||||
$themeActionsFile = theme_path('functions.php');
|
||||
if (file_exists($themeActionsFile)) {
|
||||
if ($themeActionsFile && file_exists($themeActionsFile)) {
|
||||
require $themeActionsFile;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ class FileLoader extends BaseLoader
|
||||
}
|
||||
|
||||
if (is_null($namespace) || $namespace === '*') {
|
||||
$themeTranslations = $this->loadPath(theme_path('lang'), $locale, $group);
|
||||
$themePath = theme_path('lang');
|
||||
$themeTranslations = $themePath ? $this->loadPath($themePath, $locale, $group) : [];
|
||||
$originalTranslations = $this->loadPath($this->path, $locale, $group);
|
||||
return array_merge($originalTranslations, $themeTranslations);
|
||||
}
|
||||
|
||||
@@ -94,13 +94,15 @@ function setting(string $key = null, $default = null)
|
||||
|
||||
/**
|
||||
* Get a path to a theme resource.
|
||||
* Returns null if a theme is not configured and
|
||||
* therefore a full path is not available for use.
|
||||
*/
|
||||
function theme_path(string $path = ''): string
|
||||
function theme_path(string $path = ''): ?string
|
||||
{
|
||||
$theme = config('view.theme');
|
||||
|
||||
if (!$theme) {
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
return base_path('themes/' . $theme .($path ? DIRECTORY_SEPARATOR.$path : $path));
|
||||
|
||||
621
composer.lock
generated
621
composer.lock
generated
File diff suppressed because it is too large
Load Diff
68
public/dist/app.js
vendored
68
public/dist/app.js
vendored
File diff suppressed because one or more lines are too long
2
public/dist/styles.css
vendored
2
public/dist/styles.css
vendored
File diff suppressed because one or more lines are too long
@@ -27,6 +27,7 @@ class DropdownSearch {
|
||||
this.runLocalSearch(input);
|
||||
} else {
|
||||
this.toggleLoading(true);
|
||||
this.listContainerElem.innerHTML = '';
|
||||
this.runAjaxSearch(input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,14 @@
|
||||
class SubmitOnChange {
|
||||
|
||||
setup() {
|
||||
this.$el.addEventListener('change', () => {
|
||||
this.filter = this.$opts.filter;
|
||||
|
||||
this.$el.addEventListener('change', (event) => {
|
||||
|
||||
if (this.filter && !event.target.matches(this.filter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const form = this.$el.closest('form');
|
||||
if (form) {
|
||||
form.submit();
|
||||
|
||||
@@ -3,7 +3,6 @@ import {onChildEvent} from "../services/dom";
|
||||
class UserSelect {
|
||||
|
||||
setup() {
|
||||
|
||||
this.input = this.$refs.input;
|
||||
this.userInfoContainer = this.$refs.userInfo;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import 'codemirror/mode/rust/rust';
|
||||
import 'codemirror/mode/shell/shell';
|
||||
import 'codemirror/mode/sql/sql';
|
||||
import 'codemirror/mode/toml/toml';
|
||||
import 'codemirror/mode/vb/vb';
|
||||
import 'codemirror/mode/vbscript/vbscript';
|
||||
import 'codemirror/mode/xml/xml';
|
||||
import 'codemirror/mode/yaml/yaml';
|
||||
@@ -87,6 +88,8 @@ const modeMap = {
|
||||
sql: 'text/x-sql',
|
||||
vbs: 'vbscript',
|
||||
vbscript: 'vbscript',
|
||||
'vb.net': 'text/x-vb',
|
||||
vbnet: 'text/x-vb',
|
||||
xml: 'xml',
|
||||
yaml: 'yaml',
|
||||
yml: 'yaml',
|
||||
|
||||
@@ -60,6 +60,7 @@ return [
|
||||
'no_activity' => 'No activity to show',
|
||||
'no_items' => 'No items available',
|
||||
'back_to_top' => 'Back to top',
|
||||
'skip_to_main_content' => 'Skip to main content',
|
||||
'toggle_details' => 'Toggle Details',
|
||||
'toggle_thumbnails' => 'Toggle Thumbnails',
|
||||
'details' => 'Details',
|
||||
|
||||
@@ -629,7 +629,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
}
|
||||
|
||||
.code-editor .lang-options {
|
||||
max-width: 480px;
|
||||
max-width: 540px;
|
||||
margin-bottom: $-s;
|
||||
a {
|
||||
margin-inline-end: $-xs;
|
||||
|
||||
@@ -136,6 +136,23 @@ $btt-size: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.skip-to-content-link {
|
||||
position: fixed;
|
||||
top: -$-xxl;
|
||||
left: 0;
|
||||
background-color: #FFF;
|
||||
z-index: 15;
|
||||
border-radius: 0 4px 4px 0;
|
||||
display: block;
|
||||
box-shadow: $bs-dark;
|
||||
font-weight: bold;
|
||||
&:focus {
|
||||
top: $-xl;
|
||||
outline-offset: -10px;
|
||||
outline: 2px dotted var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.contained-search-box {
|
||||
display: flex;
|
||||
height: 38px;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
</head>
|
||||
<body class="@yield('body-class')">
|
||||
|
||||
@include('common.parts.skip-to-content')
|
||||
@include('partials.notifications')
|
||||
@include('common.header')
|
||||
|
||||
|
||||
1
resources/views/common/parts/skip-to-content.blade.php
Normal file
1
resources/views/common/parts/skip-to-content.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
<a class="px-m py-s skip-to-content-link" href="#main-content">{{ trans('common.skip_to_main_content') }}</a>
|
||||
@@ -35,6 +35,7 @@
|
||||
<a refs="code-editor@languageLink" data-lang="shell">Shell/Bash</a>
|
||||
<a refs="code-editor@languageLink" data-lang="SQL">SQL</a>
|
||||
<a refs="code-editor@languageLink" data-lang="VBScript">VBScript</a>
|
||||
<a refs="code-editor@languageLink" data-lang="VB.NET">VB.NET</a>
|
||||
<a refs="code-editor@languageLink" data-lang="XML">XML</a>
|
||||
<a refs="code-editor@languageLink" data-lang="YAML">YAML</a>
|
||||
</small>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="container small py-xl">
|
||||
|
||||
<main class="card content-wrap auto-height">
|
||||
<div class="body">
|
||||
<div id="main-content" class="body">
|
||||
<h3>{{ trans('errors.error_occurred') }}</h3>
|
||||
<h5 class="mb-m">{{ $message ?? 'An unknown error occurred' }}</h5>
|
||||
<p><a href="{{ url('/') }}" class="button outline">{{ trans('errors.return_home') }}</a></p>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="flex-fill flex fill-height">
|
||||
<div id="main-content" class="flex-fill flex fill-height">
|
||||
<form action="{{ $page->getUrl() }}" autocomplete="off" data-page-id="{{ $page->id }}" method="POST" class="flex flex-fill">
|
||||
{{ csrf_field() }}
|
||||
|
||||
|
||||
@@ -41,7 +41,9 @@
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<div class="form-group ml-auto" component="submit-on-change">
|
||||
<div class="form-group ml-auto"
|
||||
component="submit-on-change"
|
||||
option:submit-on-change:filter='[name="user"]'>
|
||||
<label for="owner">{{ trans('settings.audit_table_user') }}</label>
|
||||
@include('components.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user', 'compact' => true])
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="flex-fill flex">
|
||||
<div class="content flex">
|
||||
<div class="scroll-body">
|
||||
<div id="main-content" class="scroll-body">
|
||||
@yield('body')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
|
||||
<div class="@yield('body-wrap-classes') tri-layout-middle">
|
||||
<div class="tri-layout-middle-contents">
|
||||
<div id="main-content" class="tri-layout-middle-contents">
|
||||
@yield('body')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -74,8 +74,11 @@
|
||||
<div role="presentation">@icon('auth/'. $driver, ['style' => 'width: 56px;height: 56px;'])</div>
|
||||
<div>
|
||||
@if($user->hasSocialAccount($driver))
|
||||
<a href="{{ url("/login/service/{$driver}/detach") }}" aria-label="{{ trans('settings.users_social_disconnect') }} - {{ $driver }}"
|
||||
class="button small outline">{{ trans('settings.users_social_disconnect') }}</a>
|
||||
<form action="{{ url("/login/service/{$driver}/detach") }}" method="POST">
|
||||
{{ csrf_field() }}
|
||||
<button aria-label="{{ trans('settings.users_social_disconnect') }} - {{ $driver }}"
|
||||
class="button small outline">{{ trans('settings.users_social_disconnect') }}</button>
|
||||
</form>
|
||||
@else
|
||||
<a href="{{ url("/login/service/{$driver}") }}" aria-label="{{ trans('settings.users_social_connect') }} - {{ $driver }}"
|
||||
class="button small outline">{{ trans('settings.users_social_connect') }}</a>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<h2 id="recent-pages" class="list-heading">
|
||||
{{ trans('entities.recently_created_pages') }}
|
||||
@if (count($recentlyCreated['pages']) > 0)
|
||||
<a href="{{ url('/search?term=' . urlencode('{created_by:'.$user->id.'} {type:page}') ) }}" class="text-small ml-s">{{ trans('common.view_all') }}</a>
|
||||
<a href="{{ url('/search?term=' . urlencode('{created_by:'.$user->slug.'} {type:page}') ) }}" class="text-small ml-s">{{ trans('common.view_all') }}</a>
|
||||
@endif
|
||||
</h2>
|
||||
@if (count($recentlyCreated['pages']) > 0)
|
||||
@@ -74,7 +74,7 @@
|
||||
<h2 id="recent-chapters" class="list-heading">
|
||||
{{ trans('entities.recently_created_chapters') }}
|
||||
@if (count($recentlyCreated['chapters']) > 0)
|
||||
<a href="{{ url('/search?term=' . urlencode('{created_by:'.$user->id.'} {type:chapter}') ) }}" class="text-small ml-s">{{ trans('common.view_all') }}</a>
|
||||
<a href="{{ url('/search?term=' . urlencode('{created_by:'.$user->slug.'} {type:chapter}') ) }}" class="text-small ml-s">{{ trans('common.view_all') }}</a>
|
||||
@endif
|
||||
</h2>
|
||||
@if (count($recentlyCreated['chapters']) > 0)
|
||||
@@ -88,7 +88,7 @@
|
||||
<h2 id="recent-books" class="list-heading">
|
||||
{{ trans('entities.recently_created_books') }}
|
||||
@if (count($recentlyCreated['books']) > 0)
|
||||
<a href="{{ url('/search?term=' . urlencode('{created_by:'.$user->id.'} {type:book}') ) }}" class="text-small ml-s">{{ trans('common.view_all') }}</a>
|
||||
<a href="{{ url('/search?term=' . urlencode('{created_by:'.$user->slug.'} {type:book}') ) }}" class="text-small ml-s">{{ trans('common.view_all') }}</a>
|
||||
@endif
|
||||
</h2>
|
||||
@if (count($recentlyCreated['books']) > 0)
|
||||
@@ -102,7 +102,7 @@
|
||||
<h2 id="recent-shelves" class="list-heading">
|
||||
{{ trans('entities.recently_created_shelves') }}
|
||||
@if (count($recentlyCreated['shelves']) > 0)
|
||||
<a href="{{ url('/search?term=' . urlencode('{created_by:'.$user->id.'} {type:bookshelf}') ) }}" class="text-small ml-s">{{ trans('common.view_all') }}</a>
|
||||
<a href="{{ url('/search?term=' . urlencode('{created_by:'.$user->slug.'} {type:bookshelf}') ) }}" class="text-small ml-s">{{ trans('common.view_all') }}</a>
|
||||
@endif
|
||||
</h2>
|
||||
@if (count($recentlyCreated['shelves']) > 0)
|
||||
|
||||
@@ -14,7 +14,7 @@ Route::group(['middleware' => 'auth'], function () {
|
||||
|
||||
// Shelves
|
||||
Route::get('/create-shelf', 'BookshelfController@create');
|
||||
Route::group(['prefix' => 'shelves'], function() {
|
||||
Route::group(['prefix' => 'shelves'], function () {
|
||||
Route::get('/', 'BookshelfController@index');
|
||||
Route::post('/', 'BookshelfController@store');
|
||||
Route::get('/{slug}/edit', 'BookshelfController@edit');
|
||||
@@ -226,7 +226,7 @@ Route::group(['middleware' => 'auth'], function () {
|
||||
Route::get('/login/service/{socialDriver}', 'Auth\SocialController@login');
|
||||
Route::get('/login/service/{socialDriver}/callback', 'Auth\SocialController@callback');
|
||||
Route::group(['middleware' => 'auth'], function () {
|
||||
Route::get('/login/service/{socialDriver}/detach', 'Auth\SocialController@detach');
|
||||
Route::post('/login/service/{socialDriver}/detach', 'Auth\SocialController@detach');
|
||||
});
|
||||
Route::get('/register/service/{socialDriver}', 'Auth\SocialController@register');
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php namespace Tests\Auth;
|
||||
|
||||
use BookStack\Auth\SocialAccount;
|
||||
use BookStack\Auth\User;
|
||||
use DB;
|
||||
use Laravel\Socialite\Contracts\Factory;
|
||||
@@ -83,6 +84,31 @@ class SocialAuthTest extends TestCase
|
||||
$resp->assertDontSee("login-form");
|
||||
}
|
||||
|
||||
public function test_social_account_detach()
|
||||
{
|
||||
$editor = $this->getEditor();
|
||||
config([
|
||||
'GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc',
|
||||
'APP_URL' => 'http://localhost'
|
||||
]);
|
||||
|
||||
$socialAccount = SocialAccount::query()->forceCreate([
|
||||
'user_id' => $editor->id,
|
||||
'driver' => 'github',
|
||||
'driver_id' => 'logintest123',
|
||||
]);
|
||||
|
||||
$resp = $this->actingAs($editor)->get($editor->getEditUrl());
|
||||
$resp->assertElementContains('form[action$="/login/service/github/detach"]', 'Disconnect Account');
|
||||
|
||||
$resp = $this->post('/login/service/github/detach');
|
||||
$resp->assertRedirect($editor->getEditUrl());
|
||||
$resp = $this->followRedirects($resp);
|
||||
$resp->assertSee('Github account was successfully disconnected from your profile.');
|
||||
|
||||
$this->assertDatabaseMissing('social_accounts', ['id' => $socialAccount->id]);
|
||||
}
|
||||
|
||||
public function test_social_autoregister()
|
||||
{
|
||||
config([
|
||||
|
||||
@@ -83,6 +83,23 @@ class UserProfileTest extends BrowserKitTest
|
||||
->see($newUser->name);
|
||||
}
|
||||
|
||||
public function test_profile_has_search_links_in_created_entity_lists()
|
||||
{
|
||||
$user = $this->getEditor();
|
||||
$resp = $this->actingAs($this->getAdmin())->visit('/user/' . $user->slug);
|
||||
|
||||
$expectedLinks = [
|
||||
'/search?term=%7Bcreated_by%3A' . $user->slug . '%7D+%7Btype%3Apage%7D',
|
||||
'/search?term=%7Bcreated_by%3A' . $user->slug . '%7D+%7Btype%3Achapter%7D',
|
||||
'/search?term=%7Bcreated_by%3A' . $user->slug . '%7D+%7Btype%3Abook%7D',
|
||||
'/search?term=%7Bcreated_by%3A' . $user->slug . '%7D+%7Btype%3Abookshelf%7D',
|
||||
];
|
||||
|
||||
foreach ($expectedLinks as $link) {
|
||||
$resp->seeInElement('[href$="' . $link . '"]', 'View All');
|
||||
}
|
||||
}
|
||||
|
||||
public function test_guest_profile_shows_limited_form()
|
||||
{
|
||||
$this->asAdmin()
|
||||
|
||||
Reference in New Issue
Block a user