Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11a1a6fb16 | ||
|
|
882c609296 | ||
|
|
77ad819970 | ||
|
|
2835e5be93 | ||
|
|
856fca8289 | ||
|
|
48d0095aa2 | ||
|
|
176a0dcd59 | ||
|
|
94b0f70bfa | ||
|
|
36d7ff77a9 | ||
|
|
fb16ac326f | ||
|
|
5947f59a04 | ||
|
|
1843d80fb7 | ||
|
|
08b2a77d41 | ||
|
|
3e8e9a23cf | ||
|
|
1253711c7d | ||
|
|
963d8f4693 | ||
|
|
0de4d6d223 | ||
|
|
06f694bad2 | ||
|
|
58b83b64c8 | ||
|
|
dfe4cde6ee | ||
|
|
41689a1e65 | ||
|
|
2ae8026903 | ||
|
|
dcb36b27a0 | ||
|
|
83082c32ef | ||
|
|
1e112f78d8 | ||
|
|
9283f28e31 | ||
|
|
7f5fc9fbe3 | ||
|
|
ce566bea2a | ||
|
|
63ce3c9add | ||
|
|
f0470afb4c | ||
|
|
f8e6172582 | ||
|
|
7a8505f812 | ||
|
|
9806907d53 | ||
|
|
2b3726702d | ||
|
|
2b46b00f29 | ||
|
|
536ad14276 | ||
|
|
a318775cfc | ||
|
|
9e0b8a9fb6 | ||
|
|
7c692ec588 | ||
|
|
da0dc7292c | ||
|
|
045710ea08 | ||
|
|
c6ad16dba6 | ||
|
|
4ea1f0c633 | ||
|
|
f5077c17f4 | ||
|
|
c73773930e | ||
|
|
1782618c64 | ||
|
|
a01bb92989 | ||
|
|
a2bcf765a8 | ||
|
|
130dc05517 | ||
|
|
572d8b3700 | ||
|
|
e0d9380055 | ||
|
|
15647a0409 | ||
|
|
e88dbe4db3 | ||
|
|
84c501bcf4 | ||
|
|
c8b6f622f4 | ||
|
|
ef211a76ae | ||
|
|
d11144d9e2 | ||
|
|
f96b0ea5f3 | ||
|
|
b4e29d2b7d | ||
|
|
2732d8961f | ||
|
|
b2f863e1f1 | ||
|
|
1df7497c09 | ||
|
|
d29a2a647a | ||
|
|
43f32f6d5a | ||
|
|
921131f999 | ||
|
|
0cde2704d0 | ||
|
|
db4093d523 | ||
|
|
049d6ba5b2 | ||
|
|
e33b587b87 | ||
|
|
c8be6ee8a6 | ||
|
|
46e6e239dc | ||
|
|
eb653bda16 | ||
|
|
9e1c8ec82a | ||
|
|
2cd7a48044 | ||
|
|
d089623aac | ||
|
|
8d7febe482 | ||
|
|
815f8d79ed | ||
|
|
b62dab32e0 | ||
|
|
9d15688a43 | ||
|
|
033b163675 | ||
|
|
6eadf3efb3 | ||
|
|
f83cc83877 | ||
|
|
17215431ca | ||
|
|
90c543064b | ||
|
|
a709fd04b5 | ||
|
|
4a1d060eb9 | ||
|
|
e17cdab420 | ||
|
|
2d074caf72 | ||
|
|
99202b3bb8 | ||
|
|
73eac83afe | ||
|
|
c11f795c1d | ||
|
|
4cbd1a9eb5 | ||
|
|
07626669da |
@@ -42,7 +42,7 @@ APP_TIMEZONE=UTC
|
||||
# overrides can be made. Defaults to disabled.
|
||||
APP_THEME=false
|
||||
|
||||
# Trusted Proxies
|
||||
# Trusted proxies
|
||||
# Used to indicate trust of systems that proxy to the application so
|
||||
# certain header values (Such as "X-Forwarded-For") can be used from the
|
||||
# incoming proxy request to provide origin detail.
|
||||
@@ -58,6 +58,13 @@ DB_DATABASE=database_database
|
||||
DB_USERNAME=database_username
|
||||
DB_PASSWORD=database_user_password
|
||||
|
||||
# MySQL specific connection options
|
||||
# Path to Certificate Authority (CA) certificate file for your MySQL instance.
|
||||
# When this option is used host name identity verification will be performed
|
||||
# which checks the hostname, used by the client, against names within the
|
||||
# certificate itself (Common Name or Subject Alternative Name).
|
||||
MYSQL_ATTR_SSL_CA="/path/to/ca.pem"
|
||||
|
||||
# Mail system to use
|
||||
# Can be 'smtp' or 'sendmail'
|
||||
MAIL_DRIVER=smtp
|
||||
@@ -297,6 +304,11 @@ RECYCLE_BIN_LIFETIME=30
|
||||
# Maximum file size, in megabytes, that can be uploaded to the system.
|
||||
FILE_UPLOAD_SIZE_LIMIT=50
|
||||
|
||||
# Export Page Size
|
||||
# Primarily used to determine page size of PDF exports.
|
||||
# Can be 'a4' or 'letter'.
|
||||
EXPORT_PAGE_SIZE=a4
|
||||
|
||||
# Allow <script> tags in page content
|
||||
# Note, if set to 'true' the page editor may still escape scripts.
|
||||
ALLOW_CONTENT_SCRIPTS=false
|
||||
@@ -319,6 +331,13 @@ ALLOW_UNTRUSTED_SERVER_FETCHING=false
|
||||
# Setting this option will also auto-adjust cookies to be SameSite=None.
|
||||
ALLOWED_IFRAME_HOSTS=null
|
||||
|
||||
# A list of sources/hostnames that can be loaded within iframes within BookStack.
|
||||
# Space separated if multiple. BookStack host domain is auto-inferred.
|
||||
# Can be set to a lone "*" to allow all sources for iframe content (Not advised).
|
||||
# Defaults to a set of common services.
|
||||
# Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
|
||||
ALLOWED_IFRAME_SOURCES="https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com"
|
||||
|
||||
# The default and maximum item-counts for listing API requests.
|
||||
API_DEFAULT_ITEM_COUNT=100
|
||||
API_MAX_ITEM_COUNT=500
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/api_request.yml
vendored
@@ -1,6 +1,5 @@
|
||||
name: New API Endpoint or API Ability
|
||||
description: Request a new endpoint or API feature be added
|
||||
title: "[API Request]: "
|
||||
labels: [":nut_and_bolt: API Request"]
|
||||
body:
|
||||
- type: textarea
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve or fix things
|
||||
title: "[Bug Report]: "
|
||||
labels: [":bug: Bug"]
|
||||
body:
|
||||
- type: textarea
|
||||
@@ -36,6 +35,15 @@ body:
|
||||
description: Provide any additional context and screenshots here to help us solve this issue
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: browserdetails
|
||||
attributes:
|
||||
label: Browser Details
|
||||
description: |
|
||||
If this is an issue that occurs when using the BookStack interface, please provide details of the browser used which presents the reported issue.
|
||||
placeholder: (eg. Firefox 97 (64-bit) on Windows 11)
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: bsversion
|
||||
attributes:
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Feature Request
|
||||
description: Request a new language to be added to CrowdIn for you to translate
|
||||
title: "[Feature Request]: "
|
||||
description: Request a new feature or idea to be added to BookStack
|
||||
labels: [":hammer: Feature Request"]
|
||||
body:
|
||||
- type: textarea
|
||||
@@ -13,8 +12,41 @@ body:
|
||||
- type: textarea
|
||||
id: benefits
|
||||
attributes:
|
||||
label: Describe the benefits this feature would bring to BookStack users
|
||||
description: Explain the measurable benefits this feature would achieve for existing BookStack users
|
||||
label: Describe the benefits this would bring to existing BookStack users
|
||||
description: |
|
||||
Explain the measurable benefits this feature would achieve for existing BookStack users.
|
||||
These benefits should details outcomes in terms of what this request solves/achieves, and should not be specific to implementation.
|
||||
This helps us understand the core desired goal so that a variety of potential implementations could be explored.
|
||||
This field is important. Lack if input here may lead to early issue closure.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: already_achieved
|
||||
attributes:
|
||||
label: Can the goal of this request already be achieved via other means?
|
||||
description: |
|
||||
Yes/No. If yes, please describe how the requested approach fits in with the existing method.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: confirm-search
|
||||
attributes:
|
||||
label: Have you searched for an existing open/closed issue?
|
||||
description: |
|
||||
To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue) for any existing issues that cover the fundemental benefit/goal of your request.
|
||||
options:
|
||||
- label: I have searched for existing issues and none cover my fundemental request
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: existing_usage
|
||||
attributes:
|
||||
label: How long have you been using BookStack?
|
||||
options:
|
||||
- Not using yet, just scoping
|
||||
- 0 to 6 months
|
||||
- 6 months to 1 year
|
||||
- 1 to 5 years
|
||||
- Over 5 years
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/language_request.yml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Language Request
|
||||
description: Request a new language to be added to CrowdIn for you to translate
|
||||
title: "[Language Request]: "
|
||||
labels: [":earth_africa: Translations"]
|
||||
assignees:
|
||||
- ssddanbrown
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/support_request.yml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Support Request
|
||||
description: Request support for a specific problem you have not been able to solve yourself
|
||||
title: "[Support Request]: "
|
||||
labels: [":dog2: Support"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
|
||||
19
.github/translators.txt
vendored
@@ -158,7 +158,7 @@ HenrijsS :: Latvian
|
||||
Pascal R-B (pborgner) :: German
|
||||
Boris (Ginfred) :: Russian
|
||||
Jonas Anker Rasmussen (jonasanker) :: Danish
|
||||
Gerwin de Keijzer (gdekeijzer) :: Dutch; German; German Informal
|
||||
Gerwin de Keijzer (gdekeijzer) :: Dutch; German Informal; German
|
||||
kometchtech :: Japanese
|
||||
Auri (Atalonica) :: Catalan
|
||||
Francesco Franchina (ffranchina) :: Italian
|
||||
@@ -215,3 +215,20 @@ Saeed (saeed205) :: Persian
|
||||
Julesdevops :: French
|
||||
peter cerny (posli.to.semka) :: Slovak
|
||||
Pavel Karlin (pavelkarlin) :: Russian
|
||||
SmokingCrop :: Dutch
|
||||
Maciej Lebiest (Szwendacz) :: Polish
|
||||
DiscordDigital :: German; German Informal
|
||||
Gábor Marton (dodver) :: Hungarian
|
||||
Jasell :: Swedish
|
||||
Ghost_chu (ghostchu) :: Chinese Simplified
|
||||
Ravid Shachar (ravidshachar) :: Hebrew
|
||||
Helga Guchshenskaya (guchshenskaya) :: Russian
|
||||
daniel chou (chou0214) :: Chinese Traditional
|
||||
Manolis PATRIARCHE (m.patriarche) :: French
|
||||
Mohammed Haboubi (haboubi92) :: Arabic
|
||||
roncallyt :: Portuguese, Brazilian
|
||||
goegol :: Dutch
|
||||
msevgen :: Turkish
|
||||
Khroners :: French
|
||||
MASOUD HOSSEINY (masoudme) :: Persian
|
||||
Thomerson Roncally (roncallyt) :: Portuguese, Brazilian
|
||||
|
||||
11
.github/workflows/phpstan.yml
vendored
@@ -1,19 +1,14 @@
|
||||
name: phpstan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- l10n_master
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- l10n_master
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.ref != 'refs/heads/l10n_development' }}
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['7.3']
|
||||
php: ['7.4']
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
|
||||
11
.github/workflows/phpunit.yml
vendored
@@ -1,19 +1,14 @@
|
||||
name: phpunit
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- l10n_master
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- l10n_master
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.ref != 'refs/heads/l10n_development' }}
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['7.3', '7.4', '8.0', '8.1']
|
||||
php: ['7.4', '8.0', '8.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
|
||||
11
.github/workflows/test-migrations.yml
vendored
@@ -1,19 +1,14 @@
|
||||
name: test-migrations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- l10n_master
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- l10n_master
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.ref != 'refs/heads/l10n_development' }}
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['7.3', '7.4', '8.0', '8.1']
|
||||
php: ['7.4', '8.0', '8.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
namespace BookStack\Api;
|
||||
|
||||
use BookStack\Http\Controllers\Api\ApiController;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
@@ -100,11 +102,37 @@ class ApiDocsGenerator
|
||||
$this->controllerClasses[$className] = $class;
|
||||
}
|
||||
|
||||
$rules = $class->getValdationRules()[$methodName] ?? [];
|
||||
$rules = collect($class->getValidationRules()[$methodName] ?? [])->map(function ($validations) {
|
||||
return array_map(function ($validation) {
|
||||
return $this->getValidationAsString($validation);
|
||||
}, $validations);
|
||||
})->toArray();
|
||||
|
||||
return empty($rules) ? null : $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given validation message to a readable string.
|
||||
*/
|
||||
protected function getValidationAsString($validation): string
|
||||
{
|
||||
if (is_string($validation)) {
|
||||
return $validation;
|
||||
}
|
||||
|
||||
if (is_object($validation) && method_exists($validation, '__toString')) {
|
||||
return strval($validation);
|
||||
}
|
||||
|
||||
if ($validation instanceof Password) {
|
||||
return 'min:8';
|
||||
}
|
||||
|
||||
$class = get_class($validation);
|
||||
|
||||
throw new Exception("Cannot provide string representation of rule for class: {$class}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse out the description text from a class method comment.
|
||||
*/
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace BookStack\Api;
|
||||
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ListingResponseBuilder
|
||||
@@ -12,6 +14,11 @@ class ListingResponseBuilder
|
||||
protected $request;
|
||||
protected $fields;
|
||||
|
||||
/**
|
||||
* @var array<callable>
|
||||
*/
|
||||
protected $resultModifiers = [];
|
||||
|
||||
protected $filterOperators = [
|
||||
'eq' => '=',
|
||||
'ne' => '!=',
|
||||
@@ -24,6 +31,7 @@ class ListingResponseBuilder
|
||||
|
||||
/**
|
||||
* ListingResponseBuilder constructor.
|
||||
* The given fields will be forced visible within the model results.
|
||||
*/
|
||||
public function __construct(Builder $query, Request $request, array $fields)
|
||||
{
|
||||
@@ -35,12 +43,16 @@ class ListingResponseBuilder
|
||||
/**
|
||||
* Get the response from this builder.
|
||||
*/
|
||||
public function toResponse()
|
||||
public function toResponse(): JsonResponse
|
||||
{
|
||||
$filteredQuery = $this->filterQuery($this->query);
|
||||
|
||||
$total = $filteredQuery->count();
|
||||
$data = $this->fetchData($filteredQuery);
|
||||
$data = $this->fetchData($filteredQuery)->each(function ($model) {
|
||||
foreach ($this->resultModifiers as $modifier) {
|
||||
$modifier($model);
|
||||
}
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'data' => $data,
|
||||
@@ -49,7 +61,17 @@ class ListingResponseBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the data to return in the response.
|
||||
* Add a callback to modify each element of the results.
|
||||
*
|
||||
* @param (callable(Model)) $modifier
|
||||
*/
|
||||
public function modifyResults($modifier): void
|
||||
{
|
||||
$this->resultModifiers[] = $modifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the data to return within the response.
|
||||
*/
|
||||
protected function fetchData(Builder $query): Collection
|
||||
{
|
||||
|
||||
@@ -84,7 +84,7 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
|
||||
try {
|
||||
$user = $this->createNewFromLdapAndCreds($userDetails, $credentials);
|
||||
} catch (UserRegistrationException $exception) {
|
||||
throw new LoginAttemptException($exception->message);
|
||||
throw new LoginAttemptException($exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
app/Auth/Access/Oidc/OidcException.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
use Exception;
|
||||
|
||||
class OidcException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace BookStack\Auth\Access\Oidc;
|
||||
|
||||
class OidcIssuerDiscoveryException extends \Exception
|
||||
use Exception;
|
||||
|
||||
class OidcIssuerDiscoveryException extends Exception
|
||||
{
|
||||
}
|
||||
|
||||
@@ -60,8 +60,11 @@ class OidcJwtSigningKey
|
||||
*/
|
||||
protected function loadFromJwkArray(array $jwk)
|
||||
{
|
||||
if ($jwk['alg'] !== 'RS256') {
|
||||
throw new OidcInvalidKeyException("Only RS256 keys are currently supported. Found key using {$jwk['alg']}");
|
||||
// 'alg' is optional for a JWK, but we will still attempt to validate if
|
||||
// it exists otherwise presume it will be compatible.
|
||||
$alg = $jwk['alg'] ?? null;
|
||||
if ($jwk['kty'] !== 'RSA' || !(is_null($alg) || $alg === 'RS256')) {
|
||||
throw new OidcInvalidKeyException("Only RS256 keys are currently supported. Found key using {$alg}");
|
||||
}
|
||||
|
||||
if (empty($jwk['use'])) {
|
||||
|
||||
@@ -164,7 +164,9 @@ class OidcProviderSettings
|
||||
protected function filterKeys(array $keys): array
|
||||
{
|
||||
return array_filter($keys, function (array $key) {
|
||||
return $key['kty'] === 'RSA' && $key['use'] === 'sig' && $key['alg'] === 'RS256';
|
||||
$alg = $key['alg'] ?? null;
|
||||
|
||||
return $key['kty'] === 'RSA' && $key['use'] === 'sig' && (is_null($alg) || $alg === 'RS256');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,12 @@ use BookStack\Auth\Access\LoginService;
|
||||
use BookStack\Auth\Access\RegistrationService;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\JsonDebugException;
|
||||
use BookStack\Exceptions\OpenIdConnectException;
|
||||
use BookStack\Exceptions\StoppedAuthenticationException;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use function config;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
|
||||
use Psr\Http\Client\ClientInterface as HttpClient;
|
||||
use function trans;
|
||||
use function url;
|
||||
@@ -25,9 +23,9 @@ use function url;
|
||||
*/
|
||||
class OidcService
|
||||
{
|
||||
protected $registrationService;
|
||||
protected $loginService;
|
||||
protected $httpClient;
|
||||
protected RegistrationService $registrationService;
|
||||
protected LoginService $loginService;
|
||||
protected HttpClient $httpClient;
|
||||
|
||||
/**
|
||||
* OpenIdService constructor.
|
||||
@@ -42,6 +40,8 @@ class OidcService
|
||||
/**
|
||||
* Initiate an authorization flow.
|
||||
*
|
||||
* @throws OidcException
|
||||
*
|
||||
* @return array{url: string, state: string}
|
||||
*/
|
||||
public function login(): array
|
||||
@@ -57,14 +57,15 @@ class OidcService
|
||||
|
||||
/**
|
||||
* Process the Authorization response from the authorization server and
|
||||
* return the matching, or new if registration active, user matched to
|
||||
* the authorization server.
|
||||
* Returns null if not authenticated.
|
||||
* return the matching, or new if registration active, user matched to the
|
||||
* authorization server. Throws if the user cannot be auth if not authenticated.
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws ClientExceptionInterface
|
||||
* @throws JsonDebugException
|
||||
* @throws OidcException
|
||||
* @throws StoppedAuthenticationException
|
||||
* @throws IdentityProviderException
|
||||
*/
|
||||
public function processAuthorizeResponse(?string $authorizationCode): ?User
|
||||
public function processAuthorizeResponse(?string $authorizationCode): User
|
||||
{
|
||||
$settings = $this->getProviderSettings();
|
||||
$provider = $this->getProvider($settings);
|
||||
@@ -78,8 +79,7 @@ class OidcService
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OidcIssuerDiscoveryException
|
||||
* @throws ClientExceptionInterface
|
||||
* @throws OidcException
|
||||
*/
|
||||
protected function getProviderSettings(): OidcProviderSettings
|
||||
{
|
||||
@@ -100,7 +100,11 @@ class OidcService
|
||||
|
||||
// Run discovery
|
||||
if ($config['discover'] ?? false) {
|
||||
$settings->discoverFromIssuer($this->httpClient, Cache::store(null), 15);
|
||||
try {
|
||||
$settings->discoverFromIssuer($this->httpClient, Cache::store(null), 15);
|
||||
} catch (OidcIssuerDiscoveryException $exception) {
|
||||
throw new OidcException('OIDC Discovery Error: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$settings->validate();
|
||||
@@ -161,9 +165,8 @@ class OidcService
|
||||
* Processes a received access token for a user. Login the user when
|
||||
* they exist, optionally registering them automatically.
|
||||
*
|
||||
* @throws OpenIdConnectException
|
||||
* @throws OidcException
|
||||
* @throws JsonDebugException
|
||||
* @throws UserRegistrationException
|
||||
* @throws StoppedAuthenticationException
|
||||
*/
|
||||
protected function processAccessTokenCallback(OidcAccessToken $accessToken, OidcProviderSettings $settings): User
|
||||
@@ -182,28 +185,28 @@ class OidcService
|
||||
try {
|
||||
$idToken->validate($settings->clientId);
|
||||
} catch (OidcInvalidTokenException $exception) {
|
||||
throw new OpenIdConnectException("ID token validate failed with error: {$exception->getMessage()}");
|
||||
throw new OidcException("ID token validate failed with error: {$exception->getMessage()}");
|
||||
}
|
||||
|
||||
$userDetails = $this->getUserDetails($idToken);
|
||||
$isLoggedIn = auth()->check();
|
||||
|
||||
if (empty($userDetails['email'])) {
|
||||
throw new OpenIdConnectException(trans('errors.oidc_no_email_address'));
|
||||
throw new OidcException(trans('errors.oidc_no_email_address'));
|
||||
}
|
||||
|
||||
if ($isLoggedIn) {
|
||||
throw new OpenIdConnectException(trans('errors.oidc_already_logged_in'), '/login');
|
||||
throw new OidcException(trans('errors.oidc_already_logged_in'));
|
||||
}
|
||||
|
||||
$user = $this->registrationService->findOrRegister(
|
||||
$userDetails['name'],
|
||||
$userDetails['email'],
|
||||
$userDetails['external_id']
|
||||
);
|
||||
|
||||
if ($user === null) {
|
||||
throw new OpenIdConnectException(trans('errors.oidc_user_not_registered', ['name' => $userDetails['external_id']]), '/login');
|
||||
try {
|
||||
$user = $this->registrationService->findOrRegister(
|
||||
$userDetails['name'],
|
||||
$userDetails['email'],
|
||||
$userDetails['external_id']
|
||||
);
|
||||
} catch (UserRegistrationException $exception) {
|
||||
throw new OidcException($exception->getMessage());
|
||||
}
|
||||
|
||||
$this->loginService->login($user, 'oidc');
|
||||
|
||||
@@ -96,7 +96,8 @@ class RegistrationService
|
||||
}
|
||||
|
||||
// Create the user
|
||||
$newUser = $this->userRepo->registerNew($userData, $emailConfirmed);
|
||||
$newUser = $this->userRepo->createWithoutActivity($userData, $emailConfirmed);
|
||||
$newUser->attachDefaultRole();
|
||||
|
||||
// Assign social account if given
|
||||
if ($socialAccount) {
|
||||
|
||||
39
app/Auth/Queries/AllUsersPaginatedAndSorted.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Queries;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* Get all the users with their permissions in a paginated format.
|
||||
* Note: Due to the use of email search this should only be used when
|
||||
* user is assumed to be trusted. (Admin users).
|
||||
* Email search can be abused to extract email addresses.
|
||||
*/
|
||||
class AllUsersPaginatedAndSorted
|
||||
{
|
||||
/**
|
||||
* @param array{sort: string, order: string, search: string} $sortData
|
||||
*/
|
||||
public function run(int $count, array $sortData): LengthAwarePaginator
|
||||
{
|
||||
$sort = $sortData['sort'];
|
||||
|
||||
$query = User::query()->select(['*'])
|
||||
->scopes(['withLastActivityAt'])
|
||||
->with(['roles', 'avatar'])
|
||||
->withCount('mfaValues')
|
||||
->orderBy($sort, $sortData['order']);
|
||||
|
||||
if ($sortData['search']) {
|
||||
$term = '%' . $sortData['search'] . '%';
|
||||
$query->where(function ($query) use ($term) {
|
||||
$query->where('name', 'like', $term)
|
||||
->orWhere('email', 'like', $term);
|
||||
});
|
||||
}
|
||||
|
||||
return $query->paginate($count);
|
||||
}
|
||||
}
|
||||
30
app/Auth/Queries/UserContentCounts.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Queries;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Page;
|
||||
|
||||
/**
|
||||
* Get asset created counts for the given user.
|
||||
*/
|
||||
class UserContentCounts
|
||||
{
|
||||
/**
|
||||
* @return array{pages: int, chapters: int, books: int, shelves: int}
|
||||
*/
|
||||
public function run(User $user): array
|
||||
{
|
||||
$createdBy = ['created_by' => $user->id];
|
||||
|
||||
return [
|
||||
'pages' => Page::visible()->where($createdBy)->count(),
|
||||
'chapters' => Chapter::visible()->where($createdBy)->count(),
|
||||
'books' => Book::visible()->where($createdBy)->count(),
|
||||
'shelves' => Bookshelf::visible()->where($createdBy)->count(),
|
||||
];
|
||||
}
|
||||
}
|
||||
37
app/Auth/Queries/UserRecentlyCreatedContent.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Auth\Queries;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
/**
|
||||
* Get the recently created content for the provided user.
|
||||
*/
|
||||
class UserRecentlyCreatedContent
|
||||
{
|
||||
/**
|
||||
* @return array{pages: Collection, chapters: Collection, books: Collection, shelves: Collection}
|
||||
*/
|
||||
public function run(User $user, int $count): array
|
||||
{
|
||||
$query = function (Builder $query) use ($user, $count) {
|
||||
return $query->orderBy('created_at', 'desc')
|
||||
->where('created_by', '=', $user->id)
|
||||
->take($count)
|
||||
->get();
|
||||
};
|
||||
|
||||
return [
|
||||
'pages' => $query(Page::visible()->where('draft', '=', false)),
|
||||
'chapters' => $query(Chapter::visible()),
|
||||
'books' => $query(Book::visible()),
|
||||
'shelves' => $query(Bookshelf::visible()),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ class Role extends Model implements Loggable
|
||||
|
||||
protected $fillable = ['display_name', 'description', 'external_auth_id'];
|
||||
|
||||
protected $hidden = ['pivot'];
|
||||
|
||||
/**
|
||||
* The roles that belong to the role.
|
||||
*/
|
||||
|
||||
@@ -72,22 +72,20 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password', 'remember_token', 'system_name', 'email_confirmed', 'external_auth_id', 'email',
|
||||
'created_at', 'updated_at', 'image_id',
|
||||
'created_at', 'updated_at', 'image_id', 'roles', 'avatar', 'user_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* This holds the user's permissions when loaded.
|
||||
*
|
||||
* @var ?Collection
|
||||
*/
|
||||
protected $permissions;
|
||||
protected ?Collection $permissions;
|
||||
|
||||
/**
|
||||
* This holds the default user when loaded.
|
||||
*
|
||||
* @var null|User
|
||||
*/
|
||||
protected static $defaultUser = null;
|
||||
protected static ?User $defaultUser = null;
|
||||
|
||||
/**
|
||||
* Returns the default public user.
|
||||
@@ -146,7 +144,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
*/
|
||||
public function attachDefaultRole(): void
|
||||
{
|
||||
$roleId = setting('registration-role');
|
||||
$roleId = intval(setting('registration-role'));
|
||||
if ($roleId && $this->roles()->where('id', '=', $roleId)->count() === 0) {
|
||||
$this->roles()->attach($roleId);
|
||||
}
|
||||
|
||||
@@ -2,30 +2,29 @@
|
||||
|
||||
namespace BookStack\Auth;
|
||||
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Auth\Access\UserInviteService;
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Exceptions\NotifyException;
|
||||
use BookStack\Exceptions\UserUpdateException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Uploads\UserAvatars;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UserRepo
|
||||
{
|
||||
protected $userAvatar;
|
||||
protected UserAvatars $userAvatar;
|
||||
protected UserInviteService $inviteService;
|
||||
|
||||
/**
|
||||
* UserRepo constructor.
|
||||
*/
|
||||
public function __construct(UserAvatars $userAvatar)
|
||||
public function __construct(UserAvatars $userAvatar, UserInviteService $inviteService)
|
||||
{
|
||||
$this->userAvatar = $userAvatar;
|
||||
$this->inviteService = $inviteService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,70 +52,164 @@ class UserRepo
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the users with their permissions.
|
||||
* Create a new basic instance of user with the given pre-validated data.
|
||||
*
|
||||
* @param array{name: string, email: string, password: ?string, external_auth_id: ?string, language: ?string, roles: ?array} $data
|
||||
*/
|
||||
public function getAllUsers(): Collection
|
||||
public function createWithoutActivity(array $data, bool $emailConfirmed = false): User
|
||||
{
|
||||
return User::query()->with('roles', 'avatar')->orderBy('name', 'asc')->get();
|
||||
}
|
||||
$user = new User();
|
||||
$user->name = $data['name'];
|
||||
$user->email = $data['email'];
|
||||
$user->password = bcrypt(empty($data['password']) ? Str::random(32) : $data['password']);
|
||||
$user->email_confirmed = $emailConfirmed;
|
||||
$user->external_auth_id = $data['external_auth_id'] ?? '';
|
||||
|
||||
/**
|
||||
* Get all the users with their permissions in a paginated format.
|
||||
* Note: Due to the use of email search this should only be used when
|
||||
* user is assumed to be trusted. (Admin users).
|
||||
* Email search can be abused to extract email addresses.
|
||||
*/
|
||||
public function getAllUsersPaginatedAndSorted(int $count, array $sortData): LengthAwarePaginator
|
||||
{
|
||||
$sort = $sortData['sort'];
|
||||
$user->refreshSlug();
|
||||
$user->save();
|
||||
|
||||
$query = User::query()->select(['*'])
|
||||
->scopes(['withLastActivityAt'])
|
||||
->with(['roles', 'avatar'])
|
||||
->withCount('mfaValues')
|
||||
->orderBy($sort, $sortData['order']);
|
||||
|
||||
if ($sortData['search']) {
|
||||
$term = '%' . $sortData['search'] . '%';
|
||||
$query->where(function ($query) use ($term) {
|
||||
$query->where('name', 'like', $term)
|
||||
->orWhere('email', 'like', $term);
|
||||
});
|
||||
if (!empty($data['language'])) {
|
||||
setting()->putUser($user, 'language', $data['language']);
|
||||
}
|
||||
|
||||
return $query->paginate($count);
|
||||
}
|
||||
if (isset($data['roles'])) {
|
||||
$this->setUserRoles($user, $data['roles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user and attaches a role to them.
|
||||
*/
|
||||
public function registerNew(array $data, bool $emailConfirmed = false): User
|
||||
{
|
||||
$user = $this->create($data, $emailConfirmed);
|
||||
$user->attachDefaultRole();
|
||||
$this->downloadAndAssignUserAvatar($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a user to a system-level role.
|
||||
* As per "createWithoutActivity" but records a "create" activity.
|
||||
*
|
||||
* @throws NotFoundException
|
||||
* @param array{name: string, email: string, password: ?string, external_auth_id: ?string, language: ?string, roles: ?array} $data
|
||||
*/
|
||||
public function attachSystemRole(User $user, string $systemRoleName)
|
||||
public function create(array $data, bool $sendInvite = false): User
|
||||
{
|
||||
$role = Role::getSystemRole($systemRoleName);
|
||||
if (is_null($role)) {
|
||||
throw new NotFoundException("Role '{$systemRoleName}' not found");
|
||||
$user = $this->createWithoutActivity($data, true);
|
||||
|
||||
if ($sendInvite) {
|
||||
$this->inviteService->sendInvitation($user);
|
||||
}
|
||||
|
||||
Activity::add(ActivityType::USER_CREATE, $user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given user with the given data.
|
||||
*
|
||||
* @param array{name: ?string, email: ?string, external_auth_id: ?string, password: ?string, roles: ?array<int>, language: ?string} $data
|
||||
*
|
||||
* @throws UserUpdateException
|
||||
*/
|
||||
public function update(User $user, array $data, bool $manageUsersAllowed): User
|
||||
{
|
||||
if (!empty($data['name'])) {
|
||||
$user->name = $data['name'];
|
||||
$user->refreshSlug();
|
||||
}
|
||||
|
||||
if (!empty($data['email']) && $manageUsersAllowed) {
|
||||
$user->email = $data['email'];
|
||||
}
|
||||
|
||||
if (!empty($data['external_auth_id']) && $manageUsersAllowed) {
|
||||
$user->external_auth_id = $data['external_auth_id'];
|
||||
}
|
||||
|
||||
if (isset($data['roles']) && $manageUsersAllowed) {
|
||||
$this->setUserRoles($user, $data['roles']);
|
||||
}
|
||||
|
||||
if (!empty($data['password'])) {
|
||||
$user->password = bcrypt($data['password']);
|
||||
}
|
||||
|
||||
if (!empty($data['language'])) {
|
||||
setting()->putUser($user, 'language', $data['language']);
|
||||
}
|
||||
|
||||
$user->save();
|
||||
Activity::add(ActivityType::USER_UPDATE, $user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given user from storage, Delete all related content.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function destroy(User $user, ?int $newOwnerId = null)
|
||||
{
|
||||
$this->ensureDeletable($user);
|
||||
|
||||
$user->socialAccounts()->delete();
|
||||
$user->apiTokens()->delete();
|
||||
$user->favourites()->delete();
|
||||
$user->mfaValues()->delete();
|
||||
$user->delete();
|
||||
|
||||
// Delete user profile images
|
||||
$this->userAvatar->destroyAllForUser($user);
|
||||
|
||||
if (!empty($newOwnerId)) {
|
||||
$newOwner = User::query()->find($newOwnerId);
|
||||
if (!is_null($newOwner)) {
|
||||
$this->migrateOwnership($user, $newOwner);
|
||||
}
|
||||
}
|
||||
|
||||
Activity::add(ActivityType::USER_DELETE, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotifyException
|
||||
*/
|
||||
protected function ensureDeletable(User $user): void
|
||||
{
|
||||
if ($this->isOnlyAdmin($user)) {
|
||||
throw new NotifyException(trans('errors.users_cannot_delete_only_admin'), $user->getEditUrl());
|
||||
}
|
||||
|
||||
if ($user->system_name === 'public') {
|
||||
throw new NotifyException(trans('errors.users_cannot_delete_guest'), $user->getEditUrl());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate ownership of items in the system from one user to another.
|
||||
*/
|
||||
protected function migrateOwnership(User $fromUser, User $toUser)
|
||||
{
|
||||
$entities = (new EntityProvider())->all();
|
||||
foreach ($entities as $instance) {
|
||||
$instance->newQuery()->where('owned_by', '=', $fromUser->id)
|
||||
->update(['owned_by' => $toUser->id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an avatar image for a user and set it as their avatar.
|
||||
* Returns early if avatars disabled or not set in config.
|
||||
*/
|
||||
protected function downloadAndAssignUserAvatar(User $user): void
|
||||
{
|
||||
try {
|
||||
$this->userAvatar->fetchAndAssignToUser($user);
|
||||
} catch (Exception $e) {
|
||||
Log::error('Failed to save user avatar image');
|
||||
}
|
||||
$user->attachRole($role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the give user is the only admin.
|
||||
*/
|
||||
public function isOnlyAdmin(User $user): bool
|
||||
protected function isOnlyAdmin(User $user): bool
|
||||
{
|
||||
if (!$user->hasSystemRole('admin')) {
|
||||
return false;
|
||||
@@ -135,7 +228,7 @@ class UserRepo
|
||||
*
|
||||
* @throws UserUpdateException
|
||||
*/
|
||||
public function setUserRoles(User $user, array $roles)
|
||||
protected function setUserRoles(User $user, array $roles)
|
||||
{
|
||||
if ($this->demotingLastAdmin($user, $roles)) {
|
||||
throw new UserUpdateException(trans('errors.role_cannot_remove_only_admin'), $user->getEditUrl());
|
||||
@@ -159,117 +252,4 @@ class UserRepo
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new basic instance of user.
|
||||
*/
|
||||
public function create(array $data, bool $emailConfirmed = false): User
|
||||
{
|
||||
$details = [
|
||||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'password' => bcrypt($data['password']),
|
||||
'email_confirmed' => $emailConfirmed,
|
||||
'external_auth_id' => $data['external_auth_id'] ?? '',
|
||||
];
|
||||
|
||||
$user = new User();
|
||||
$user->forceFill($details);
|
||||
$user->refreshSlug();
|
||||
$user->save();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given user from storage, Delete all related content.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function destroy(User $user, ?int $newOwnerId = null)
|
||||
{
|
||||
$user->socialAccounts()->delete();
|
||||
$user->apiTokens()->delete();
|
||||
$user->favourites()->delete();
|
||||
$user->mfaValues()->delete();
|
||||
$user->delete();
|
||||
|
||||
// Delete user profile images
|
||||
$this->userAvatar->destroyAllForUser($user);
|
||||
|
||||
if (!empty($newOwnerId)) {
|
||||
$newOwner = User::query()->find($newOwnerId);
|
||||
if (!is_null($newOwner)) {
|
||||
$this->migrateOwnership($user, $newOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate ownership of items in the system from one user to another.
|
||||
*/
|
||||
protected function migrateOwnership(User $fromUser, User $toUser)
|
||||
{
|
||||
$entities = (new EntityProvider())->all();
|
||||
foreach ($entities as $instance) {
|
||||
$instance->newQuery()->where('owned_by', '=', $fromUser->id)
|
||||
->update(['owned_by' => $toUser->id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the recently created content for this given user.
|
||||
*/
|
||||
public function getRecentlyCreated(User $user, int $count = 20): array
|
||||
{
|
||||
$query = function (Builder $query) use ($user, $count) {
|
||||
return $query->orderBy('created_at', 'desc')
|
||||
->where('created_by', '=', $user->id)
|
||||
->take($count)
|
||||
->get();
|
||||
};
|
||||
|
||||
return [
|
||||
'pages' => $query(Page::visible()->where('draft', '=', false)),
|
||||
'chapters' => $query(Chapter::visible()),
|
||||
'books' => $query(Book::visible()),
|
||||
'shelves' => $query(Bookshelf::visible()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get asset created counts for the give user.
|
||||
*/
|
||||
public function getAssetCounts(User $user): array
|
||||
{
|
||||
$createdBy = ['created_by' => $user->id];
|
||||
|
||||
return [
|
||||
'pages' => Page::visible()->where($createdBy)->count(),
|
||||
'chapters' => Chapter::visible()->where($createdBy)->count(),
|
||||
'books' => Book::visible()->where($createdBy)->count(),
|
||||
'shelves' => Bookshelf::visible()->where($createdBy)->count(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the roles in the system that are assignable to a user.
|
||||
*/
|
||||
public function getAllRoles(): Collection
|
||||
{
|
||||
return Role::query()->orderBy('display_name', 'asc')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an avatar image for a user and set it as their avatar.
|
||||
* Returns early if avatars disabled or not set in config.
|
||||
*/
|
||||
public function downloadAndAssignUserAvatar(User $user): void
|
||||
{
|
||||
try {
|
||||
$this->userAvatar->fetchAndAssignToUser($user);
|
||||
} catch (Exception $e) {
|
||||
Log::error('Failed to save user avatar image');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,13 @@ return [
|
||||
// Space separated if multiple. BookStack host domain is auto-inferred.
|
||||
'iframe_hosts' => env('ALLOWED_IFRAME_HOSTS', null),
|
||||
|
||||
// A list of sources/hostnames that can be loaded within iframes within BookStack.
|
||||
// Space separated if multiple. BookStack host domain is auto-inferred.
|
||||
// Can be set to a lone "*" to allow all sources for iframe content (Not advised).
|
||||
// Defaults to a set of common services.
|
||||
// Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
|
||||
'iframe_sources' => env('ALLOWED_IFRAME_SOURCES', 'https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com'),
|
||||
|
||||
// Application timezone for back-end date functions.
|
||||
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
* Configuration should be altered via the `.env` file or environment variables.
|
||||
* Do not edit this file unless you're happy to maintain any changes yourself.
|
||||
*/
|
||||
$dompdfPaperSizeMap = [
|
||||
'a4' => 'a4',
|
||||
'letter' => 'letter',
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
@@ -150,7 +154,7 @@ return [
|
||||
*
|
||||
* @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.)
|
||||
*/
|
||||
'default_paper_size' => 'a4',
|
||||
'default_paper_size' => $dompdfPaperSizeMap[env('EXPORT_PAGE_SIZE', 'a4')] ?? 'a4',
|
||||
|
||||
/**
|
||||
* The default font family.
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
* Configuration should be altered via the `.env` file or environment variables.
|
||||
* Do not edit this file unless you're happy to maintain any changes yourself.
|
||||
*/
|
||||
$snappyPaperSizeMap = [
|
||||
'a4' => 'A4',
|
||||
'letter' => 'Letter',
|
||||
];
|
||||
|
||||
return [
|
||||
'pdf' => [
|
||||
@@ -14,7 +18,8 @@ return [
|
||||
'binary' => file_exists(base_path('wkhtmltopdf')) ? base_path('wkhtmltopdf') : env('WKHTMLTOPDF', false),
|
||||
'timeout' => false,
|
||||
'options' => [
|
||||
'outline' => true,
|
||||
'outline' => true,
|
||||
'page-size' => $snappyPaperSizeMap[env('EXPORT_PAGE_SIZE', 'a4')] ?? 'A4',
|
||||
],
|
||||
'env' => [],
|
||||
],
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
use Symfony\Component\Console\Command\Command as SymfonyCommand;
|
||||
@@ -19,7 +22,8 @@ class CreateAdmin extends Command
|
||||
protected $signature = 'bookstack:create-admin
|
||||
{--email= : The email address for the new admin user}
|
||||
{--name= : The name of the new admin user}
|
||||
{--password= : The password to assign to the new admin user}';
|
||||
{--password= : The password to assign to the new admin user}
|
||||
{--external-auth-id= : The external authentication system id for the new admin user (SAML2/LDAP/OIDC)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -42,28 +46,35 @@ class CreateAdmin extends Command
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @throws \BookStack\Exceptions\NotFoundException
|
||||
* @throws NotFoundException
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$details = $this->options();
|
||||
$details = $this->snakeCaseOptions();
|
||||
|
||||
if (empty($details['email'])) {
|
||||
$details['email'] = $this->ask('Please specify an email address for the new admin user');
|
||||
}
|
||||
|
||||
if (empty($details['name'])) {
|
||||
$details['name'] = $this->ask('Please specify a name for the new admin user');
|
||||
}
|
||||
|
||||
if (empty($details['password'])) {
|
||||
$details['password'] = $this->ask('Please specify a password for the new admin user (8 characters min)');
|
||||
if (empty($details['external_auth_id'])) {
|
||||
$details['password'] = $this->ask('Please specify a password for the new admin user (8 characters min)');
|
||||
} else {
|
||||
$details['password'] = Str::random(32);
|
||||
}
|
||||
}
|
||||
|
||||
$validator = Validator::make($details, [
|
||||
'email' => ['required', 'email', 'min:5', new Unique('users', 'email')],
|
||||
'name' => ['required', 'min:2'],
|
||||
'password' => ['required', Password::default()],
|
||||
'email' => ['required', 'email', 'min:5', new Unique('users', 'email')],
|
||||
'name' => ['required', 'min:2'],
|
||||
'password' => ['required_without:external_auth_id', Password::default()],
|
||||
'external_auth_id' => ['required_without:password'],
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
@@ -74,9 +85,8 @@ class CreateAdmin extends Command
|
||||
return SymfonyCommand::FAILURE;
|
||||
}
|
||||
|
||||
$user = $this->userRepo->create($validator->validated());
|
||||
$this->userRepo->attachSystemRole($user, 'admin');
|
||||
$this->userRepo->downloadAndAssignUserAvatar($user);
|
||||
$user = $this->userRepo->createWithoutActivity($validator->validated());
|
||||
$user->attachRole(Role::getSystemRole('admin'));
|
||||
$user->email_confirmed = true;
|
||||
$user->save();
|
||||
|
||||
@@ -84,4 +94,14 @@ class CreateAdmin extends Command
|
||||
|
||||
return SymfonyCommand::SUCCESS;
|
||||
}
|
||||
|
||||
protected function snakeCaseOptions(): array
|
||||
{
|
||||
$returnOpts = [];
|
||||
foreach ($this->options() as $key => $value) {
|
||||
$returnOpts[str_replace('-', '_', $key)] = $value;
|
||||
}
|
||||
|
||||
return $returnOpts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ class DeleteUsers extends Command
|
||||
*/
|
||||
protected $signature = 'bookstack:delete-users';
|
||||
|
||||
protected $user;
|
||||
|
||||
protected $userRepo;
|
||||
|
||||
/**
|
||||
@@ -26,9 +24,8 @@ class DeleteUsers extends Command
|
||||
*/
|
||||
protected $description = 'Delete users that are not "admin" or system users';
|
||||
|
||||
public function __construct(User $user, UserRepo $userRepo)
|
||||
public function __construct(UserRepo $userRepo)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->userRepo = $userRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -38,8 +35,8 @@ class DeleteUsers extends Command
|
||||
$confirm = $this->ask('This will delete all users from the system that are not "admin" or system users. Are you sure you want to continue? (Type "yes" to continue)');
|
||||
$numDeleted = 0;
|
||||
if (strtolower(trim($confirm)) === 'yes') {
|
||||
$totalUsers = $this->user->count();
|
||||
$users = $this->user->where('system_name', '=', null)->with('roles')->get();
|
||||
$totalUsers = User::query()->count();
|
||||
$users = User::query()->whereNull('system_name')->with('roles')->get();
|
||||
foreach ($users as $user) {
|
||||
if ($user->hasSystemRole('admin')) {
|
||||
// don't delete users with "admin" role
|
||||
|
||||
@@ -7,6 +7,7 @@ use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
|
||||
use BookStack\Uploads\ImageService;
|
||||
use BookStack\Util\CspService;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMXPath;
|
||||
@@ -15,16 +16,18 @@ use Throwable;
|
||||
|
||||
class ExportFormatter
|
||||
{
|
||||
protected $imageService;
|
||||
protected $pdfGenerator;
|
||||
protected ImageService $imageService;
|
||||
protected PdfGenerator $pdfGenerator;
|
||||
protected CspService $cspService;
|
||||
|
||||
/**
|
||||
* ExportService constructor.
|
||||
*/
|
||||
public function __construct(ImageService $imageService, PdfGenerator $pdfGenerator)
|
||||
public function __construct(ImageService $imageService, PdfGenerator $pdfGenerator, CspService $cspService)
|
||||
{
|
||||
$this->imageService = $imageService;
|
||||
$this->pdfGenerator = $pdfGenerator;
|
||||
$this->cspService = $cspService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,8 +40,9 @@ class ExportFormatter
|
||||
{
|
||||
$page->html = (new PageContent($page))->render();
|
||||
$pageHtml = view('pages.export', [
|
||||
'page' => $page,
|
||||
'format' => 'html',
|
||||
'page' => $page,
|
||||
'format' => 'html',
|
||||
'cspContent' => $this->cspService->getCspMetaTagValue(),
|
||||
])->render();
|
||||
|
||||
return $this->containHtml($pageHtml);
|
||||
@@ -56,9 +60,10 @@ class ExportFormatter
|
||||
$page->html = (new PageContent($page))->render();
|
||||
});
|
||||
$html = view('chapters.export', [
|
||||
'chapter' => $chapter,
|
||||
'pages' => $pages,
|
||||
'format' => 'html',
|
||||
'chapter' => $chapter,
|
||||
'pages' => $pages,
|
||||
'format' => 'html',
|
||||
'cspContent' => $this->cspService->getCspMetaTagValue(),
|
||||
])->render();
|
||||
|
||||
return $this->containHtml($html);
|
||||
@@ -76,6 +81,7 @@ class ExportFormatter
|
||||
'book' => $book,
|
||||
'bookChildren' => $bookTree,
|
||||
'format' => 'html',
|
||||
'cspContent' => $this->cspService->getCspMetaTagValue(),
|
||||
])->render();
|
||||
|
||||
return $this->containHtml($html);
|
||||
@@ -147,10 +153,31 @@ class ExportFormatter
|
||||
{
|
||||
$html = $this->containHtml($html);
|
||||
$html = $this->replaceIframesWithLinks($html);
|
||||
$html = $this->openDetailElements($html);
|
||||
|
||||
return $this->pdfGenerator->fromHtml($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Within the given HTML content, Open any detail blocks.
|
||||
*/
|
||||
protected function openDetailElements(string $html): string
|
||||
{
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
|
||||
$xPath = new DOMXPath($doc);
|
||||
|
||||
$details = $xPath->query('//details');
|
||||
/** @var DOMElement $detail */
|
||||
foreach ($details as $detail) {
|
||||
$detail->setAttribute('open', 'open');
|
||||
}
|
||||
|
||||
return $doc->saveHTML();
|
||||
}
|
||||
|
||||
/**
|
||||
* Within the given HTML content, replace any iframe elements
|
||||
* with anchor links within paragraph blocks.
|
||||
|
||||
@@ -109,15 +109,35 @@ class PageContent
|
||||
|
||||
/**
|
||||
* Convert all inline base64 content to uploaded image files.
|
||||
* Regex is used to locate the start of data-uri definitions then
|
||||
* manual looping over content is done to parse the whole data uri.
|
||||
* Attempting to capture the whole data uri using regex can cause PHP
|
||||
* PCRE limits to be hit with larger, multi-MB, files.
|
||||
*/
|
||||
protected function extractBase64ImagesFromMarkdown(string $markdown)
|
||||
{
|
||||
$matches = [];
|
||||
preg_match_all('/!\[.*?]\(.*?(data:image\/.*?)[)"\s]/', $markdown, $matches);
|
||||
$contentLength = strlen($markdown);
|
||||
$replacements = [];
|
||||
preg_match_all('/!\[.*?]\(.*?(data:image\/.{1,6};base64,)/', $markdown, $matches, PREG_OFFSET_CAPTURE);
|
||||
|
||||
foreach ($matches[1] as $base64Match) {
|
||||
$newUrl = $this->base64ImageUriToUploadedImageUrl($base64Match);
|
||||
$markdown = str_replace($base64Match, $newUrl, $markdown);
|
||||
foreach ($matches[1] as $base64MatchPair) {
|
||||
[$dataUri, $index] = $base64MatchPair;
|
||||
|
||||
for ($i = strlen($dataUri) + $index; $i < $contentLength; $i++) {
|
||||
$char = $markdown[$i];
|
||||
if ($char === ')' || $char === ' ' || $char === "\n" || $char === '"') {
|
||||
break;
|
||||
}
|
||||
$dataUri .= $char;
|
||||
}
|
||||
|
||||
$newUrl = $this->base64ImageUriToUploadedImageUrl($dataUri);
|
||||
$replacements[] = [$dataUri, $newUrl];
|
||||
}
|
||||
|
||||
foreach ($replacements as [$dataUri, $newUrl]) {
|
||||
$markdown = str_replace($dataUri, $newUrl, $markdown);
|
||||
}
|
||||
|
||||
return $markdown;
|
||||
@@ -219,6 +239,9 @@ class PageContent
|
||||
$html .= $doc->saveHTML($childNode);
|
||||
}
|
||||
|
||||
// Perform required string-level tweaks
|
||||
$html = str_replace(' ', ' ', $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,10 @@ class Handler extends ExceptionHandler
|
||||
$code = $e->status;
|
||||
}
|
||||
|
||||
if (method_exists($e, 'getStatus')) {
|
||||
$code = $e->getStatus();
|
||||
}
|
||||
|
||||
$responseData['error']['code'] = $code;
|
||||
|
||||
return new JsonResponse($responseData, $code, $headers);
|
||||
|
||||
@@ -3,23 +3,25 @@
|
||||
namespace BookStack\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class JsonDebugException extends Exception
|
||||
{
|
||||
protected $data;
|
||||
protected array $data;
|
||||
|
||||
/**
|
||||
* JsonDebugException constructor.
|
||||
*/
|
||||
public function __construct($data)
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Covert this exception into a response.
|
||||
*/
|
||||
public function render()
|
||||
public function render(): JsonResponse
|
||||
{
|
||||
return response()->json($this->data);
|
||||
}
|
||||
|
||||
@@ -9,17 +9,24 @@ class NotifyException extends Exception implements Responsable
|
||||
{
|
||||
public $message;
|
||||
public $redirectLocation;
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* NotifyException constructor.
|
||||
*/
|
||||
public function __construct(string $message, string $redirectLocation = '/')
|
||||
public function __construct(string $message, string $redirectLocation = '/', int $status = 500)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->redirectLocation = $redirectLocation;
|
||||
$this->status = $status;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the desired status code for this exception.
|
||||
*/
|
||||
public function getStatus(): int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the response for this type of exception.
|
||||
*
|
||||
@@ -29,6 +36,11 @@ class NotifyException extends Exception implements Responsable
|
||||
{
|
||||
$message = $this->getMessage();
|
||||
|
||||
// Front-end JSON handling. API-side handling managed via handler.
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json(['error' => $message], 403);
|
||||
}
|
||||
|
||||
if (!empty($message)) {
|
||||
session()->flash('error', $message);
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Exceptions;
|
||||
|
||||
class OpenIdConnectException extends NotifyException
|
||||
{
|
||||
}
|
||||
@@ -15,10 +15,14 @@ abstract class ApiController extends Controller
|
||||
* Provide a paginated listing JSON response in a standard format
|
||||
* taking into account any pagination parameters passed by the user.
|
||||
*/
|
||||
protected function apiListingResponse(Builder $query, array $fields): JsonResponse
|
||||
protected function apiListingResponse(Builder $query, array $fields, array $modifiers = []): JsonResponse
|
||||
{
|
||||
$listing = new ListingResponseBuilder($query, request(), $fields);
|
||||
|
||||
foreach ($modifiers as $modifier) {
|
||||
$listing->modifyResults($modifier);
|
||||
}
|
||||
|
||||
return $listing->toResponse();
|
||||
}
|
||||
|
||||
@@ -26,7 +30,7 @@ abstract class ApiController extends Controller
|
||||
* Get the validation rules for this controller.
|
||||
* Defaults to a $rules property but can be a rules() method.
|
||||
*/
|
||||
public function getValdationRules(): array
|
||||
public function getValidationRules(): array
|
||||
{
|
||||
if (method_exists($this, 'rules')) {
|
||||
return $this->rules();
|
||||
|
||||
168
app/Http/Controllers/Api/UserApiController.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Http\Controllers\Api;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\UserUpdateException;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
|
||||
class UserApiController extends ApiController
|
||||
{
|
||||
protected $userRepo;
|
||||
|
||||
protected $fieldsToExpose = [
|
||||
'email', 'created_at', 'updated_at', 'last_activity_at', 'external_auth_id',
|
||||
];
|
||||
|
||||
public function __construct(UserRepo $userRepo)
|
||||
{
|
||||
$this->userRepo = $userRepo;
|
||||
|
||||
// Checks for all endpoints in this controller
|
||||
$this->middleware(function ($request, $next) {
|
||||
$this->checkPermission('users-manage');
|
||||
$this->preventAccessInDemoMode();
|
||||
|
||||
return $next($request);
|
||||
});
|
||||
}
|
||||
|
||||
protected function rules(int $userId = null): array
|
||||
{
|
||||
return [
|
||||
'create' => [
|
||||
'name' => ['required', 'min:2'],
|
||||
'email' => [
|
||||
'required', 'min:2', 'email', new Unique('users', 'email'),
|
||||
],
|
||||
'external_auth_id' => ['string'],
|
||||
'language' => ['string'],
|
||||
'password' => [Password::default()],
|
||||
'roles' => ['array'],
|
||||
'roles.*' => ['integer'],
|
||||
'send_invite' => ['boolean'],
|
||||
],
|
||||
'update' => [
|
||||
'name' => ['min:2'],
|
||||
'email' => [
|
||||
'min:2',
|
||||
'email',
|
||||
(new Unique('users', 'email'))->ignore($userId ?? null),
|
||||
],
|
||||
'external_auth_id' => ['string'],
|
||||
'language' => ['string'],
|
||||
'password' => [Password::default()],
|
||||
'roles' => ['array'],
|
||||
'roles.*' => ['integer'],
|
||||
],
|
||||
'delete' => [
|
||||
'migrate_ownership_id' => ['integer', 'exists:users,id'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a listing of users in the system.
|
||||
* Requires permission to manage users.
|
||||
*/
|
||||
public function list()
|
||||
{
|
||||
$users = User::query()->select(['*'])
|
||||
->scopes('withLastActivityAt')
|
||||
->with(['avatar']);
|
||||
|
||||
return $this->apiListingResponse($users, [
|
||||
'id', 'name', 'slug', 'email', 'external_auth_id',
|
||||
'created_at', 'updated_at', 'last_activity_at',
|
||||
], [Closure::fromCallable([$this, 'listFormatter'])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user in the system.
|
||||
* Requires permission to manage users.
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$data = $this->validate($request, $this->rules()['create']);
|
||||
$sendInvite = ($data['send_invite'] ?? false) === true;
|
||||
|
||||
$user = null;
|
||||
DB::transaction(function () use ($data, $sendInvite, &$user) {
|
||||
$user = $this->userRepo->create($data, $sendInvite);
|
||||
});
|
||||
|
||||
$this->singleFormatter($user);
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* View the details of a single user.
|
||||
* Requires permission to manage users.
|
||||
*/
|
||||
public function read(string $id)
|
||||
{
|
||||
$user = $this->userRepo->getById($id);
|
||||
$this->singleFormatter($user);
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing user in the system.
|
||||
* Requires permission to manage users.
|
||||
*
|
||||
* @throws UserUpdateException
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
$data = $this->validate($request, $this->rules($id)['update']);
|
||||
$user = $this->userRepo->getById($id);
|
||||
$this->userRepo->update($user, $data, userCan('users-manage'));
|
||||
$this->singleFormatter($user);
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user from the system.
|
||||
* Can optionally accept a user id via `migrate_ownership_id` to indicate
|
||||
* who should be the new owner of their related content.
|
||||
* Requires permission to manage users.
|
||||
*/
|
||||
public function delete(Request $request, string $id)
|
||||
{
|
||||
$user = $this->userRepo->getById($id);
|
||||
$newOwnerId = $request->get('migrate_ownership_id', null);
|
||||
|
||||
$this->userRepo->destroy($user, $newOwnerId);
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given user model for single-result display.
|
||||
*/
|
||||
protected function singleFormatter(User $user)
|
||||
{
|
||||
$this->listFormatter($user);
|
||||
$user->load('roles:id,display_name');
|
||||
$user->makeVisible(['roles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given user model for a listing multi-result display.
|
||||
*/
|
||||
protected function listFormatter(User $user)
|
||||
{
|
||||
$user->makeVisible($this->fieldsToExpose);
|
||||
$user->setAttribute('profile_url', $user->getProfileUrl());
|
||||
$user->setAttribute('edit_url', $user->getEditUrl());
|
||||
$user->setAttribute('avatar_url', $user->getAvatar());
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
namespace BookStack\Http\Controllers\Auth;
|
||||
|
||||
use BookStack\Auth\Access\Oidc\OidcException;
|
||||
use BookStack\Auth\Access\Oidc\OidcService;
|
||||
use BookStack\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class OidcController extends Controller
|
||||
{
|
||||
protected $oidcService;
|
||||
protected OidcService $oidcService;
|
||||
|
||||
/**
|
||||
* OpenIdController constructor.
|
||||
@@ -24,7 +25,14 @@ class OidcController extends Controller
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
$loginDetails = $this->oidcService->login();
|
||||
try {
|
||||
$loginDetails = $this->oidcService->login();
|
||||
} catch (OidcException $exception) {
|
||||
$this->showErrorNotification($exception->getMessage());
|
||||
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
session()->flash('oidc_state', $loginDetails['state']);
|
||||
|
||||
return redirect($loginDetails['url']);
|
||||
@@ -45,7 +53,13 @@ class OidcController extends Controller
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
$this->oidcService->processAuthorizeResponse($request->query('code'));
|
||||
try {
|
||||
$this->oidcService->processAuthorizeResponse($request->query('code'));
|
||||
} catch (OidcException $oidcException) {
|
||||
$this->showErrorNotification($oidcException->getMessage());
|
||||
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
return redirect()->intended();
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Exceptions\NotifyException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Model;
|
||||
use BookStack\Util\WebSafeMimeSniffer;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
@@ -53,14 +53,9 @@ abstract class Controller extends BaseController
|
||||
*/
|
||||
protected function showPermissionError()
|
||||
{
|
||||
if (request()->wantsJson()) {
|
||||
$response = response()->json(['error' => trans('errors.permissionJson')], 403);
|
||||
} else {
|
||||
$response = redirect('/');
|
||||
$this->showErrorNotification(trans('errors.permission'));
|
||||
}
|
||||
$message = request()->wantsJson() ? trans('errors.permissionJson') : trans('errors.permission');
|
||||
|
||||
throw new HttpResponseException($response);
|
||||
throw new NotifyException($message, '/', 403);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -107,14 +107,6 @@ class HomeController extends Controller
|
||||
return view('home.default', $commonData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom head HTML, Used in ajax calls to show in editor.
|
||||
*/
|
||||
public function customHeadContent()
|
||||
{
|
||||
return view('common.custom-head');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the view for /robots.txt.
|
||||
*/
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Auth\Access\SocialAuthService;
|
||||
use BookStack\Auth\Access\UserInviteService;
|
||||
use BookStack\Auth\Queries\AllUsersPaginatedAndSorted;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
@@ -13,25 +13,20 @@ use BookStack\Uploads\ImageRepo;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
protected $user;
|
||||
protected $userRepo;
|
||||
protected $inviteService;
|
||||
protected $imageRepo;
|
||||
|
||||
/**
|
||||
* UserController constructor.
|
||||
*/
|
||||
public function __construct(User $user, UserRepo $userRepo, UserInviteService $inviteService, ImageRepo $imageRepo)
|
||||
public function __construct(UserRepo $userRepo, ImageRepo $imageRepo)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->inviteService = $inviteService;
|
||||
$this->imageRepo = $imageRepo;
|
||||
}
|
||||
|
||||
@@ -46,12 +41,16 @@ class UserController extends Controller
|
||||
'search' => $request->get('search', ''),
|
||||
'sort' => $request->get('sort', 'name'),
|
||||
];
|
||||
$users = $this->userRepo->getAllUsersPaginatedAndSorted(20, $listDetails);
|
||||
|
||||
$users = (new AllUsersPaginatedAndSorted())->run(20, $listDetails);
|
||||
|
||||
$this->setPageTitle(trans('settings.users'));
|
||||
$users->appends($listDetails);
|
||||
|
||||
return view('users.index', ['users' => $users, 'listDetails' => $listDetails]);
|
||||
return view('users.index', [
|
||||
'users' => $users,
|
||||
'listDetails' => $listDetails,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,61 +60,41 @@ class UserController extends Controller
|
||||
{
|
||||
$this->checkPermission('users-manage');
|
||||
$authMethod = config('auth.method');
|
||||
$roles = $this->userRepo->getAllRoles();
|
||||
$roles = Role::query()->orderBy('display_name', 'asc')->get();
|
||||
$this->setPageTitle(trans('settings.users_add_new'));
|
||||
|
||||
return view('users.create', ['authMethod' => $authMethod, 'roles' => $roles]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created user in storage.
|
||||
* Store a new user in storage.
|
||||
*
|
||||
* @throws UserUpdateException
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->checkPermission('users-manage');
|
||||
$validationRules = [
|
||||
'name' => ['required'],
|
||||
'email' => ['required', 'email', 'unique:users,email'],
|
||||
];
|
||||
|
||||
$authMethod = config('auth.method');
|
||||
$sendInvite = ($request->get('send_invite', 'false') === 'true');
|
||||
$externalAuth = $authMethod === 'ldap' || $authMethod === 'saml2' || $authMethod === 'oidc';
|
||||
$passwordRequired = ($authMethod === 'standard' && !$sendInvite);
|
||||
|
||||
if ($authMethod === 'standard' && !$sendInvite) {
|
||||
$validationRules['password'] = ['required', Password::default()];
|
||||
$validationRules['password-confirm'] = ['required', 'same:password'];
|
||||
} elseif ($authMethod === 'ldap' || $authMethod === 'saml2' || $authMethod === 'openid') {
|
||||
$validationRules['external_auth_id'] = ['required'];
|
||||
}
|
||||
$this->validate($request, $validationRules);
|
||||
$validationRules = [
|
||||
'name' => ['required'],
|
||||
'email' => ['required', 'email', 'unique:users,email'],
|
||||
'language' => ['string'],
|
||||
'roles' => ['array'],
|
||||
'roles.*' => ['integer'],
|
||||
'password' => $passwordRequired ? ['required', Password::default()] : null,
|
||||
'password-confirm' => $passwordRequired ? ['required', 'same:password'] : null,
|
||||
'external_auth_id' => $externalAuth ? ['required'] : null,
|
||||
];
|
||||
|
||||
$user = $this->user->fill($request->all());
|
||||
$validated = $this->validate($request, array_filter($validationRules));
|
||||
|
||||
if ($authMethod === 'standard') {
|
||||
$user->password = bcrypt($request->get('password', Str::random(32)));
|
||||
} elseif ($authMethod === 'ldap' || $authMethod === 'saml2' || $authMethod === 'openid') {
|
||||
$user->external_auth_id = $request->get('external_auth_id');
|
||||
}
|
||||
|
||||
$user->refreshSlug();
|
||||
|
||||
DB::transaction(function () use ($user, $sendInvite, $request) {
|
||||
$user->save();
|
||||
|
||||
if ($sendInvite) {
|
||||
$this->inviteService->sendInvitation($user);
|
||||
}
|
||||
|
||||
if ($request->filled('roles')) {
|
||||
$roles = $request->get('roles');
|
||||
$this->userRepo->setUserRoles($user, $roles);
|
||||
}
|
||||
|
||||
$this->userRepo->downloadAndAssignUserAvatar($user);
|
||||
|
||||
$this->logActivity(ActivityType::USER_CREATE, $user);
|
||||
DB::transaction(function () use ($validated, $sendInvite) {
|
||||
$this->userRepo->create($validated, $sendInvite);
|
||||
});
|
||||
|
||||
return redirect('/settings/users');
|
||||
@@ -129,14 +108,14 @@ class UserController extends Controller
|
||||
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $this->user->newQuery()->with(['apiTokens', 'mfaValues'])->findOrFail($id);
|
||||
$user = User::query()->with(['apiTokens', 'mfaValues'])->findOrFail($id);
|
||||
|
||||
$authMethod = ($user->system_name) ? 'system' : config('auth.method');
|
||||
|
||||
$activeSocialDrivers = $socialAuthService->getActiveDrivers();
|
||||
$mfaMethods = $user->mfaValues->groupBy('method');
|
||||
$this->setPageTitle(trans('settings.user_profile'));
|
||||
$roles = $this->userRepo->getAllRoles();
|
||||
$roles = Role::query()->orderBy('display_name', 'asc')->get();
|
||||
|
||||
return view('users.edit', [
|
||||
'user' => $user,
|
||||
@@ -159,51 +138,20 @@ class UserController extends Controller
|
||||
$this->preventAccessInDemoMode();
|
||||
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
||||
|
||||
$this->validate($request, [
|
||||
$validated = $this->validate($request, [
|
||||
'name' => ['min:2'],
|
||||
'email' => ['min:2', 'email', 'unique:users,email,' . $id],
|
||||
'password' => ['required_with:password_confirm', Password::default()],
|
||||
'password-confirm' => ['same:password', 'required_with:password'],
|
||||
'setting' => ['array'],
|
||||
'language' => ['string'],
|
||||
'roles' => ['array'],
|
||||
'roles.*' => ['integer'],
|
||||
'external_auth_id' => ['string'],
|
||||
'profile_image' => array_merge(['nullable'], $this->getImageValidationRules()),
|
||||
]);
|
||||
|
||||
$user = $this->userRepo->getById($id);
|
||||
$user->fill($request->except(['email']));
|
||||
|
||||
// Email updates
|
||||
if (userCan('users-manage') && $request->filled('email')) {
|
||||
$user->email = $request->get('email');
|
||||
}
|
||||
|
||||
// Refresh the slug if the user's name has changed
|
||||
if ($user->isDirty('name')) {
|
||||
$user->refreshSlug();
|
||||
}
|
||||
|
||||
// Role updates
|
||||
if (userCan('users-manage') && $request->filled('roles')) {
|
||||
$roles = $request->get('roles');
|
||||
$this->userRepo->setUserRoles($user, $roles);
|
||||
}
|
||||
|
||||
// Password updates
|
||||
if ($request->filled('password')) {
|
||||
$password = $request->get('password');
|
||||
$user->password = bcrypt($password);
|
||||
}
|
||||
|
||||
// External auth id updates
|
||||
if (user()->can('users-manage') && $request->filled('external_auth_id')) {
|
||||
$user->external_auth_id = $request->get('external_auth_id');
|
||||
}
|
||||
|
||||
// Save an user-specific settings
|
||||
if ($request->filled('setting')) {
|
||||
foreach ($request->get('setting') as $key => $value) {
|
||||
setting()->putUser($user, $key, $value);
|
||||
}
|
||||
}
|
||||
$this->userRepo->update($user, $validated, userCan('users-manage'));
|
||||
|
||||
// Save profile image if in request
|
||||
if ($request->hasFile('profile_image')) {
|
||||
@@ -211,6 +159,7 @@ class UserController extends Controller
|
||||
$this->imageRepo->destroyImage($user->avatar);
|
||||
$image = $this->imageRepo->saveNew($imageUpload, 'user', $user->id);
|
||||
$user->image_id = $image->id;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
// Delete the profile image if reset option is in request
|
||||
@@ -218,11 +167,7 @@ class UserController extends Controller
|
||||
$this->imageRepo->destroyImage($user->avatar);
|
||||
}
|
||||
|
||||
$user->save();
|
||||
$this->showSuccessNotification(trans('settings.users_edit_success'));
|
||||
$this->logActivity(ActivityType::USER_UPDATE, $user);
|
||||
|
||||
$redirectUrl = userCan('users-manage') ? '/settings/users' : ('/settings/users/' . $user->id);
|
||||
$redirectUrl = userCan('users-manage') ? '/settings/users' : "/settings/users/{$user->id}";
|
||||
|
||||
return redirect($redirectUrl);
|
||||
}
|
||||
@@ -253,21 +198,7 @@ class UserController extends Controller
|
||||
$user = $this->userRepo->getById($id);
|
||||
$newOwnerId = $request->get('new_owner_id', null);
|
||||
|
||||
if ($this->userRepo->isOnlyAdmin($user)) {
|
||||
$this->showErrorNotification(trans('errors.users_cannot_delete_only_admin'));
|
||||
|
||||
return redirect($user->getEditUrl());
|
||||
}
|
||||
|
||||
if ($user->system_name === 'public') {
|
||||
$this->showErrorNotification(trans('errors.users_cannot_delete_guest'));
|
||||
|
||||
return redirect($user->getEditUrl());
|
||||
}
|
||||
|
||||
$this->userRepo->destroy($user, $newOwnerId);
|
||||
$this->showSuccessNotification(trans('settings.users_delete_success'));
|
||||
$this->logActivity(ActivityType::USER_DELETE, $user);
|
||||
|
||||
return redirect('/settings/users');
|
||||
}
|
||||
@@ -352,7 +283,7 @@ class UserController extends Controller
|
||||
|
||||
$newState = $request->get('expand', 'false');
|
||||
|
||||
$user = $this->user->findOrFail($id);
|
||||
$user = $this->userRepo->getById($id);
|
||||
setting()->putUser($user, 'section_expansion#' . $key, $newState);
|
||||
|
||||
return response('', 204);
|
||||
@@ -375,7 +306,7 @@ class UserController extends Controller
|
||||
$order = 'asc';
|
||||
}
|
||||
|
||||
$user = $this->user->findOrFail($userId);
|
||||
$user = $this->userRepo->getById($userId);
|
||||
$sortKey = $listName . '_sort';
|
||||
$orderKey = $listName . '_sort_order';
|
||||
setting()->putUser($user, $sortKey, $sort);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Actions\ActivityQueries;
|
||||
use BookStack\Auth\Queries\UserContentCounts;
|
||||
use BookStack\Auth\Queries\UserRecentlyCreatedContent;
|
||||
use BookStack\Auth\UserRepo;
|
||||
|
||||
class UserProfileController extends Controller
|
||||
@@ -15,8 +17,8 @@ class UserProfileController extends Controller
|
||||
$user = $repo->getBySlug($slug);
|
||||
|
||||
$userActivity = $activities->userActivity($user);
|
||||
$recentlyCreated = $repo->getRecentlyCreated($user, 5);
|
||||
$assetCounts = $repo->getAssetCounts($user);
|
||||
$recentlyCreated = (new UserRecentlyCreatedContent())->run($user, 5);
|
||||
$assetCounts = (new UserContentCounts())->run($user);
|
||||
|
||||
$this->setPageTitle($user->name);
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ use Illuminate\Http\Request;
|
||||
|
||||
class ApplyCspRules
|
||||
{
|
||||
/**
|
||||
* @var CspService
|
||||
*/
|
||||
protected $cspService;
|
||||
protected CspService $cspService;
|
||||
|
||||
public function __construct(CspService $cspService)
|
||||
{
|
||||
@@ -35,10 +32,8 @@ class ApplyCspRules
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
$this->cspService->setFrameAncestors($response);
|
||||
$this->cspService->setScriptSrc($response);
|
||||
$this->cspService->setObjectSrc($response);
|
||||
$this->cspService->setBaseUri($response);
|
||||
$cspHeader = $this->cspService->getCspHeader();
|
||||
$response->headers->set('Content-Security-Policy', $cspHeader, false);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
@@ -2,35 +2,33 @@
|
||||
|
||||
namespace BookStack\Notifications;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class UserInvite extends MailNotification
|
||||
{
|
||||
public $token;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @param string $token
|
||||
*/
|
||||
public function __construct($token)
|
||||
public function __construct(string $token)
|
||||
{
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
*
|
||||
* @return \Illuminate\Notifications\Messages\MailMessage
|
||||
*/
|
||||
public function toMail($notifiable)
|
||||
public function toMail(User $notifiable): MailMessage
|
||||
{
|
||||
$appName = ['appName' => setting('app-name')];
|
||||
$language = setting()->getUser($notifiable, 'language');
|
||||
|
||||
return $this->newMailMessage()
|
||||
->subject(trans('auth.user_invite_email_subject', $appName))
|
||||
->greeting(trans('auth.user_invite_email_greeting', $appName))
|
||||
->line(trans('auth.user_invite_email_text'))
|
||||
->action(trans('auth.user_invite_email_action'), url('/register/invite/' . $this->token));
|
||||
->subject(trans('auth.user_invite_email_subject', $appName, $language))
|
||||
->greeting(trans('auth.user_invite_email_greeting', $appName, $language))
|
||||
->line(trans('auth.user_invite_email_text', [], $language))
|
||||
->action(trans('auth.user_invite_email_action', [], $language), url('/register/invite/' . $this->token));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
// Password Configuration
|
||||
// Changes here must be reflected in ApiDocsGenerate@getValidationAsString.
|
||||
Password::defaults(function () {
|
||||
return Password::min(8);
|
||||
});
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
namespace BookStack\Util;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CspService
|
||||
{
|
||||
/** @var string */
|
||||
protected $nonce;
|
||||
protected string $nonce;
|
||||
|
||||
public function __construct(string $nonce = '')
|
||||
{
|
||||
@@ -24,37 +22,34 @@ class CspService
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets CSP 'script-src' headers to restrict the forms of script that can
|
||||
* run on the page.
|
||||
* Get the CSP headers for the application
|
||||
*/
|
||||
public function setScriptSrc(Response $response)
|
||||
public function getCspHeader(): string
|
||||
{
|
||||
if (config('app.allow_content_scripts')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parts = [
|
||||
'http:',
|
||||
'https:',
|
||||
'\'nonce-' . $this->nonce . '\'',
|
||||
'\'strict-dynamic\'',
|
||||
$headers = [
|
||||
$this->getFrameAncestors(),
|
||||
$this->getFrameSrc(),
|
||||
$this->getScriptSrc(),
|
||||
$this->getObjectSrc(),
|
||||
$this->getBaseUri(),
|
||||
];
|
||||
|
||||
$value = 'script-src ' . implode(' ', $parts);
|
||||
$response->headers->set('Content-Security-Policy', $value, false);
|
||||
return implode('; ', array_filter($headers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets CSP "frame-ancestors" headers to restrict the hosts that BookStack can be
|
||||
* iframed within. Also adjusts the cookie samesite options so that cookies will
|
||||
* operate in the third-party context.
|
||||
* Get the CSP rules for the application for a HTML meta tag.
|
||||
*/
|
||||
public function setFrameAncestors(Response $response)
|
||||
public function getCspMetaTagValue(): string
|
||||
{
|
||||
$iframeHosts = $this->getAllowedIframeHosts();
|
||||
array_unshift($iframeHosts, "'self'");
|
||||
$cspValue = 'frame-ancestors ' . implode(' ', $iframeHosts);
|
||||
$response->headers->set('Content-Security-Policy', $cspValue, false);
|
||||
$headers = [
|
||||
$this->getFrameSrc(),
|
||||
$this->getScriptSrc(),
|
||||
$this->getObjectSrc(),
|
||||
$this->getBaseUri(),
|
||||
];
|
||||
|
||||
return implode('; ', array_filter($headers));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,25 +61,65 @@ class CspService
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets CSP 'object-src' headers to restrict the types of dynamic content
|
||||
* that can be embedded on the page.
|
||||
* Create CSP 'script-src' rule to restrict the forms of script that can run on the page.
|
||||
*/
|
||||
public function setObjectSrc(Response $response)
|
||||
protected function getScriptSrc(): string
|
||||
{
|
||||
if (config('app.allow_content_scripts')) {
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
$response->headers->set('Content-Security-Policy', 'object-src \'self\'', false);
|
||||
$parts = [
|
||||
'http:',
|
||||
'https:',
|
||||
'\'nonce-' . $this->nonce . '\'',
|
||||
'\'strict-dynamic\'',
|
||||
];
|
||||
|
||||
return 'script-src ' . implode(' ', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets CSP 'base-uri' headers to restrict what base tags can be set on
|
||||
* Create CSP "frame-ancestors" rule to restrict the hosts that BookStack can be iframed within.
|
||||
*/
|
||||
protected function getFrameAncestors(): string
|
||||
{
|
||||
$iframeHosts = $this->getAllowedIframeHosts();
|
||||
array_unshift($iframeHosts, "'self'");
|
||||
return 'frame-ancestors ' . implode(' ', $iframeHosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates CSP "frame-src" rule to restrict what hosts/sources can be loaded
|
||||
* within iframes to provide an allow-list-style approach to iframe content.
|
||||
*/
|
||||
protected function getFrameSrc(): string
|
||||
{
|
||||
$iframeHosts = $this->getAllowedIframeSources();
|
||||
array_unshift($iframeHosts, "'self'");
|
||||
return 'frame-src ' . implode(' ', $iframeHosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates CSP 'object-src' rule to restrict the types of dynamic content
|
||||
* that can be embedded on the page.
|
||||
*/
|
||||
protected function getObjectSrc(): string
|
||||
{
|
||||
if (config('app.allow_content_scripts')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return "object-src 'self'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates CSP 'base-uri' rule to restrict what base tags can be set on
|
||||
* the page to prevent manipulation of relative links.
|
||||
*/
|
||||
public function setBaseUri(Response $response)
|
||||
protected function getBaseUri(): string
|
||||
{
|
||||
$response->headers->set('Content-Security-Policy', 'base-uri \'self\'', false);
|
||||
return "base-uri 'self'";
|
||||
}
|
||||
|
||||
protected function getAllowedIframeHosts(): array
|
||||
@@ -93,4 +128,21 @@ class CspService
|
||||
|
||||
return array_filter(explode(' ', $hosts));
|
||||
}
|
||||
|
||||
protected function getAllowedIframeSources(): array
|
||||
{
|
||||
$sources = config('app.iframe_sources', '');
|
||||
$hosts = array_filter(explode(' ', $sources));
|
||||
|
||||
// Extract drawing service url to allow embedding if active
|
||||
$drawioConfigValue = config('services.drawio');
|
||||
if ($drawioConfigValue) {
|
||||
$drawioSource = is_string($drawioConfigValue) ? $drawioConfigValue : 'https://embed.diagrams.net/';
|
||||
$drawioSourceParsed = parse_url($drawioSource);
|
||||
$drawioHost = $drawioSourceParsed['scheme'] . '://' . $drawioSourceParsed['host'];
|
||||
$hosts[] = $drawioHost;
|
||||
}
|
||||
|
||||
return $hosts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"license": "MIT",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.3|^8.0",
|
||||
"php": "^7.4|^8.0",
|
||||
"ext-curl": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-fileinfo": "*",
|
||||
@@ -17,8 +17,8 @@
|
||||
"ext-mbstring": "*",
|
||||
"ext-xml": "*",
|
||||
"bacon/bacon-qr-code": "^2.0",
|
||||
"barryvdh/laravel-dompdf": "^0.9.0",
|
||||
"barryvdh/laravel-snappy": "^0.4.8",
|
||||
"barryvdh/laravel-dompdf": "^1.0",
|
||||
"barryvdh/laravel-snappy": "^1.0",
|
||||
"doctrine/dbal": "^3.1",
|
||||
"filp/whoops": "^2.14",
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
@@ -95,7 +95,7 @@
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"platform": {
|
||||
"php": "7.3.0"
|
||||
"php": "7.4.0"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
|
||||
725
composer.lock
generated
7
dev/api/requests/users-create.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Dan Brown",
|
||||
"email": "dannyb@example.com",
|
||||
"roles": [1],
|
||||
"language": "fr",
|
||||
"send_invite": true
|
||||
}
|
||||
3
dev/api/requests/users-delete.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"migrate_ownership_id": 5
|
||||
}
|
||||
7
dev/api/requests/users-update.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Dan Spaggleforth",
|
||||
"email": "dspaggles@example.com",
|
||||
"roles": [2],
|
||||
"language": "de",
|
||||
"password": "hunter2000"
|
||||
}
|
||||
19
dev/api/responses/users-create.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Dan Brown",
|
||||
"email": "dannyb@example.com",
|
||||
"created_at": "2022-02-03T16:27:55.000000Z",
|
||||
"updated_at": "2022-02-03T16:27:55.000000Z",
|
||||
"external_auth_id": "abc123456",
|
||||
"slug": "dan-brown",
|
||||
"last_activity_at": "2022-02-03T16:27:55.000000Z",
|
||||
"profile_url": "https://docs.example.com/user/dan-brown",
|
||||
"edit_url": "https://docs.example.com/settings/users/1",
|
||||
"avatar_url": "https://docs.example.com/uploads/images/user/2021-10/thumbs-50-50/profile-2021.jpg",
|
||||
"roles": [
|
||||
{
|
||||
"id": 1,
|
||||
"display_name": "Admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
33
dev/api/responses/users-list.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Dan Brown",
|
||||
"email": "dannyb@example.com",
|
||||
"created_at": "2022-02-03T16:27:55.000000Z",
|
||||
"updated_at": "2022-02-03T16:27:55.000000Z",
|
||||
"external_auth_id": "abc123456",
|
||||
"slug": "dan-brown",
|
||||
"user_id": 1,
|
||||
"last_activity_at": "2022-02-03T16:27:55.000000Z",
|
||||
"profile_url": "https://docs.example.com/user/dan-brown",
|
||||
"edit_url": "https://docs.example.com/settings/users/1",
|
||||
"avatar_url": "https://docs.example.com/uploads/images/user/2021-10/thumbs-50-50/profile-2021.jpg"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Benny",
|
||||
"email": "benny@example.com",
|
||||
"created_at": "2022-01-31T20:39:24.000000Z",
|
||||
"updated_at": "2021-11-18T17:10:58.000000Z",
|
||||
"external_auth_id": "",
|
||||
"slug": "benny",
|
||||
"user_id": 2,
|
||||
"last_activity_at": "2022-01-31T20:39:24.000000Z",
|
||||
"profile_url": "https://docs.example.com/user/benny",
|
||||
"edit_url": "https://docs.example.com/settings/users/2",
|
||||
"avatar_url": "https://docs.example.com/uploads/images/user/2021-11/thumbs-50-50/guest.jpg"
|
||||
}
|
||||
],
|
||||
"total": 28
|
||||
}
|
||||
19
dev/api/responses/users-read.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Dan Brown",
|
||||
"email": "dannyb@example.com",
|
||||
"created_at": "2022-02-03T16:27:55.000000Z",
|
||||
"updated_at": "2022-02-03T16:27:55.000000Z",
|
||||
"external_auth_id": "abc123456",
|
||||
"slug": "dan-brown",
|
||||
"last_activity_at": "2022-02-03T16:27:55.000000Z",
|
||||
"profile_url": "https://docs.example.com/user/dan-brown",
|
||||
"edit_url": "https://docs.example.com/settings/users/1",
|
||||
"avatar_url": "https://docs.example.com/uploads/images/user/2021-10/thumbs-50-50/profile-2021.jpg",
|
||||
"roles": [
|
||||
{
|
||||
"id": 1,
|
||||
"display_name": "Admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
dev/api/responses/users-update.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Dan Spaggleforth",
|
||||
"email": "dspaggles@example.com",
|
||||
"created_at": "2022-02-03T16:27:55.000000Z",
|
||||
"updated_at": "2022-02-03T16:27:55.000000Z",
|
||||
"external_auth_id": "abc123456",
|
||||
"slug": "dan-spaggleforth",
|
||||
"last_activity_at": "2022-02-03T16:27:55.000000Z",
|
||||
"profile_url": "https://docs.example.com/user/dan-spaggleforth",
|
||||
"edit_url": "https://docs.example.com/settings/users/1",
|
||||
"avatar_url": "https://docs.example.com/uploads/images/user/2021-10/thumbs-50-50/profile-2021.jpg",
|
||||
"roles": [
|
||||
{
|
||||
"id": 2,
|
||||
"display_name": "Editors"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM php:7.4-apache
|
||||
FROM php:8.1-apache
|
||||
|
||||
ENV APACHE_DOCUMENT_ROOT /app/public
|
||||
WORKDIR /app
|
||||
|
||||
465
package-lock.json
generated
@@ -4,9 +4,10 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bookstack",
|
||||
"dependencies": {
|
||||
"clipboard": "^2.0.8",
|
||||
"codemirror": "^5.65.1",
|
||||
"clipboard": "^2.0.10",
|
||||
"codemirror": "^5.65.2",
|
||||
"dropzone": "^5.9.3",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
@@ -14,11 +15,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"chokidar-cli": "^3.0",
|
||||
"esbuild": "0.14.13",
|
||||
"esbuild": "0.14.23",
|
||||
"livereload": "^0.9.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"punycode": "^2.1.1",
|
||||
"sass": "^1.49.0"
|
||||
"sass": "^1.49.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
@@ -173,9 +174,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/clipboard": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
|
||||
"integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.10.tgz",
|
||||
"integrity": "sha512-cz3m2YVwFz95qSEbCDi2fzLN/epEN9zXBvfgAoGkvGOJZATMl9gtTDVOtBYkx2ODUJl2kvmud7n32sV2BpYR4g==",
|
||||
"dependencies": {
|
||||
"good-listener": "^1.2.2",
|
||||
"select": "^1.1.2",
|
||||
@@ -194,9 +195,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "5.65.1",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.1.tgz",
|
||||
"integrity": "sha512-s6aac+DD+4O2u1aBmdxhB7yz2XU7tG3snOyQ05Kxifahz7hoxnfxIRHxiCSEv3TUC38dIVH8G+lZH9UWSfGQxA=="
|
||||
"version": "5.65.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.2.tgz",
|
||||
"integrity": "sha512-SZM4Zq7XEC8Fhroqe3LxbEEX1zUPWH1wMr5zxiBuiUF64iYOUH/JI88v4tBag8MiBS8B8gRv8O1pPXGYXQ4ErA=="
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
@@ -341,39 +342,43 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.13.tgz",
|
||||
"integrity": "sha512-FIxvAdj3i2oHA6ex+E67bG7zlSTO+slt8kU2ogHDgGtrQLy2HNChv3PYjiFTYkt8hZbEAniZCXVeHn+FrHt7dA==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.23.tgz",
|
||||
"integrity": "sha512-XjnIcZ9KB6lfonCa+jRguXyRYcldmkyZ99ieDksqW/C8bnyEX299yA4QH2XcgijCgaddEZePPTgvx/2imsq7Ig==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"esbuild-android-arm64": "0.14.13",
|
||||
"esbuild-darwin-64": "0.14.13",
|
||||
"esbuild-darwin-arm64": "0.14.13",
|
||||
"esbuild-freebsd-64": "0.14.13",
|
||||
"esbuild-freebsd-arm64": "0.14.13",
|
||||
"esbuild-linux-32": "0.14.13",
|
||||
"esbuild-linux-64": "0.14.13",
|
||||
"esbuild-linux-arm": "0.14.13",
|
||||
"esbuild-linux-arm64": "0.14.13",
|
||||
"esbuild-linux-mips64le": "0.14.13",
|
||||
"esbuild-linux-ppc64le": "0.14.13",
|
||||
"esbuild-linux-s390x": "0.14.13",
|
||||
"esbuild-netbsd-64": "0.14.13",
|
||||
"esbuild-openbsd-64": "0.14.13",
|
||||
"esbuild-sunos-64": "0.14.13",
|
||||
"esbuild-windows-32": "0.14.13",
|
||||
"esbuild-windows-64": "0.14.13",
|
||||
"esbuild-windows-arm64": "0.14.13"
|
||||
"esbuild-android-arm64": "0.14.23",
|
||||
"esbuild-darwin-64": "0.14.23",
|
||||
"esbuild-darwin-arm64": "0.14.23",
|
||||
"esbuild-freebsd-64": "0.14.23",
|
||||
"esbuild-freebsd-arm64": "0.14.23",
|
||||
"esbuild-linux-32": "0.14.23",
|
||||
"esbuild-linux-64": "0.14.23",
|
||||
"esbuild-linux-arm": "0.14.23",
|
||||
"esbuild-linux-arm64": "0.14.23",
|
||||
"esbuild-linux-mips64le": "0.14.23",
|
||||
"esbuild-linux-ppc64le": "0.14.23",
|
||||
"esbuild-linux-riscv64": "0.14.23",
|
||||
"esbuild-linux-s390x": "0.14.23",
|
||||
"esbuild-netbsd-64": "0.14.23",
|
||||
"esbuild-openbsd-64": "0.14.23",
|
||||
"esbuild-sunos-64": "0.14.23",
|
||||
"esbuild-windows-32": "0.14.23",
|
||||
"esbuild-windows-64": "0.14.23",
|
||||
"esbuild-windows-arm64": "0.14.23"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-arm64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.13.tgz",
|
||||
"integrity": "sha512-rhtwl+KJ3BzzXkK09N3/YbEF1i5WhriysJEStoeWNBzchx9hlmzyWmDGQQhu56HF78ua3JrVPyLOsdLGvtMvxQ==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-k9sXem++mINrZty1v4FVt6nC5BQCFG4K2geCIUUqHNlTdFnuvcqsY7prcKZLFhqVC1rbcJAr9VSUGFL/vD4vsw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -381,12 +386,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.13.tgz",
|
||||
"integrity": "sha512-Fl47xIt5RMu50WIgMU93kwmUUJb+BPuL8R895n/aBNQqavS+KUMpLPoqKGABBV4myfx/fnAD/97X8Gt1C1YW6w==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.23.tgz",
|
||||
"integrity": "sha512-lB0XRbtOYYL1tLcYw8BoBaYsFYiR48RPrA0KfA/7RFTr4MV7Bwy/J4+7nLsVnv9FGuQummM3uJ93J3ptaTqFug==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -394,12 +402,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-arm64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.13.tgz",
|
||||
"integrity": "sha512-UttqKRFXsWvuivcyAbFmo54vdkC9Me1ZYQNuoz/uBYDbkb2MgqKYG2+xoVKPBhLvhT0CKM5QGKD81flMH5BE6A==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-yat73Z/uJ5tRcfRiI4CCTv0FSnwErm3BJQeZAh+1tIP0TUNh6o+mXg338Zl5EKChD+YGp6PN+Dbhs7qa34RxSw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -407,12 +418,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.13.tgz",
|
||||
"integrity": "sha512-dlIhPFSp29Yq2TPh7Cm3/4M0uKjlfvOylHVNCRvRNiOvDbBol6/NZ3kLisczms+Yra0rxVapBPN1oMbSMuts9g==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.23.tgz",
|
||||
"integrity": "sha512-/1xiTjoLuQ+LlbfjJdKkX45qK/M7ARrbLmyf7x3JhyQGMjcxRYVR6Dw81uH3qlMHwT4cfLW4aEVBhP1aNV7VsA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -420,12 +434,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-arm64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.13.tgz",
|
||||
"integrity": "sha512-bNOHLu7Oq6RwaAMnwPbJ40DVGPl9GlAOnfH/dFZ792f8hFEbopkbtVzo1SU1jjfY3TGLWOgqHNWxPxx1N7Au+g==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-uyPqBU/Zcp6yEAZS4LKj5jEE0q2s4HmlMBIPzbW6cTunZ8cyvjG6YWpIZXb1KK3KTJDe62ltCrk3VzmWHp+iLg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -433,12 +450,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-32": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.13.tgz",
|
||||
"integrity": "sha512-WzXyBx6zx16adGi7wPBvH2lRCBzYMcqnBRrJ8ciLIqYyruGvprZocX1nFWfiexjLcFxIElWnMNPX6LG7ULqyXA==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.23.tgz",
|
||||
"integrity": "sha512-37R/WMkQyUfNhbH7aJrr1uCjDVdnPeTHGeDhZPUNhfoHV0lQuZNCKuNnDvlH/u/nwIYZNdVvz1Igv5rY/zfrzQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -446,12 +466,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.13.tgz",
|
||||
"integrity": "sha512-P6OFAfcoUvE7g9h/0UKm3qagvTovwqpCF1wbFLWe/BcCY8BS1bR/+SxUjCeKX2BcpIsg4/43ezHDE/ntg/iOpw==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.23.tgz",
|
||||
"integrity": "sha512-H0gztDP60qqr8zoFhAO64waoN5yBXkmYCElFklpd6LPoobtNGNnDe99xOQm28+fuD75YJ7GKHzp/MLCLhw2+vQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -459,12 +482,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.13.tgz",
|
||||
"integrity": "sha512-4jmm0UySCg3Wi6FEBS7jpiPb1IyckI5um5kzYRwulHxPzkiokd6cgpcsTakR4/Y84UEicS8LnFAghHhXHZhbFg==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.23.tgz",
|
||||
"integrity": "sha512-x64CEUxi8+EzOAIpCUeuni0bZfzPw/65r8tC5cy5zOq9dY7ysOi5EVQHnzaxS+1NmV+/RVRpmrzGw1QgY2Xpmw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -472,12 +498,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.13.tgz",
|
||||
"integrity": "sha512-k/uIvmkm4mc7vyMvJVwILgGxi2F+FuvLdmESIIWoHrnxEfEekC5AWpI/R6GQ2OMfp8snebSQLs8KL05QPnt1zA==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-c4MLOIByNHR55n3KoYf9hYDfBRghMjOiHLaoYLhkQkIabb452RWi+HsNgB41sUpSlOAqfpqKPFNg7VrxL3UX9g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -485,12 +514,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-mips64le": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.13.tgz",
|
||||
"integrity": "sha512-vwYtgjQ1TRlUGL88km9wH9TjXsdZyZ/Xht1ASptg5XGRlqGquVjLGH11PfLLunoMdkQ0YTXR68b4l5gRfjVbyg==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.23.tgz",
|
||||
"integrity": "sha512-kHKyKRIAedYhKug2EJpyJxOUj3VYuamOVA1pY7EimoFPzaF3NeY7e4cFBAISC/Av0/tiV0xlFCt9q0HJ68IBIw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -498,12 +530,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-ppc64le": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.13.tgz",
|
||||
"integrity": "sha512-0KqDSIkZaYugtcdpFCd3eQ38Fg6TzhxmOpkhDIKNTwD/W2RoXeiS+Z4y5yQ3oysb/ySDOxWkwNqTdXS4sz2LdQ==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.23.tgz",
|
||||
"integrity": "sha512-7ilAiJEPuJJnJp/LiDO0oJm5ygbBPzhchJJh9HsHZzeqO+3PUzItXi+8PuicY08r0AaaOe25LA7sGJ0MzbfBag==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -511,12 +546,31 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-riscv64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.23.tgz",
|
||||
"integrity": "sha512-fbL3ggK2wY0D8I5raPIMPhpCvODFE+Bhb5QGtNP3r5aUsRR6TQV+ZBXIaw84iyvKC8vlXiA4fWLGhghAd/h/Zg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-s390x": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.13.tgz",
|
||||
"integrity": "sha512-bG20i7d0CN97fwPN9LaLe64E2IrI0fPZWEcoiff9hzzsvo/fQCx0YjMbPC2T3gqQ48QZRltdU9hQilTjHk3geQ==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.23.tgz",
|
||||
"integrity": "sha512-GHMDCyfy7+FaNSO8RJ8KCFsnax8fLUsOrj9q5Gi2JmZMY0Zhp75keb5abTFCq2/Oy6KVcT0Dcbyo/bFb4rIFJA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -524,12 +578,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-netbsd-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.13.tgz",
|
||||
"integrity": "sha512-jz96PQb0ltqyqLggPpcRbWxzLvWHvrZBHZQyjcOzKRDqg1fR/R1y10b1Cuv84xoIbdAf+ceNUJkMN21FfR9G2g==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.23.tgz",
|
||||
"integrity": "sha512-ovk2EX+3rrO1M2lowJfgMb/JPN1VwVYrx0QPUyudxkxLYrWeBxDKQvc6ffO+kB4QlDyTfdtAURrVzu3JeNdA2g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -537,12 +594,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-openbsd-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.13.tgz",
|
||||
"integrity": "sha512-bp6zSo3kDCXKPM5MmVUg6DEpt+yXDx37iDGzNTn3Kf9xh6d0cdITxUC4Bx6S3Di79GVYubWs+wNjSRVFIJpryw==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.23.tgz",
|
||||
"integrity": "sha512-uYYNqbVR+i7k8ojP/oIROAHO9lATLN7H2QeXKt2H310Fc8FJj4y3Wce6hx0VgnJ4k1JDrgbbiXM8rbEgQyg8KA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -550,12 +610,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.13.tgz",
|
||||
"integrity": "sha512-08Fne1T9QHYxUnu55sV9V4i/yECADOaI1zMGET2YUa8SRkib10i80hc89U7U/G02DxpN/KUJMWEGq2wKTn0QFQ==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.23.tgz",
|
||||
"integrity": "sha512-hAzeBeET0+SbScknPzS2LBY6FVDpgE+CsHSpe6CEoR51PApdn2IB0SyJX7vGelXzlyrnorM4CAsRyb9Qev4h9g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -563,12 +626,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-32": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.13.tgz",
|
||||
"integrity": "sha512-MW3BMIi9+fzTyDdljH0ftfT/qlD3t+aVzle1O+zZ2MgHRMQD20JwWgyqoJXhe6uDVyunrAUbcjH3qTIEZN3isg==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.23.tgz",
|
||||
"integrity": "sha512-Kttmi3JnohdaREbk6o9e25kieJR379TsEWF0l39PQVHXq3FR6sFKtVPgY8wk055o6IB+rllrzLnbqOw/UV60EA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -576,12 +642,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.13.tgz",
|
||||
"integrity": "sha512-d7+0N+EOgBKdi/nMxlQ8QA5xHBlpcLtSrYnHsA+Xp4yZk28dYfRw1+embsHf5uN5/1iPvrJwPrcpgDH1xyy4JA==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.23.tgz",
|
||||
"integrity": "sha512-JtIT0t8ymkpl6YlmOl6zoSWL5cnCgyLaBdf/SiU/Eg3C13r0NbHZWNT/RDEMKK91Y6t79kTs3vyRcNZbfu5a8g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -589,12 +658,15 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-arm64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.13.tgz",
|
||||
"integrity": "sha512-oX5hmgXk9yNKbb5AxThzRQm/E9kiHyDll7JJeyeT1fuGENTifv33f0INCpjBQ+Ty5ChKc84++ZQTEBwLCA12Kw==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-cTFaQqT2+ik9e4hePvYtRZQ3pqOvKDVNarzql0VFIzhc0tru/ZgdLoXd6epLiKT+SzoSce6V9YJ+nn6RCn6SHw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -602,7 +674,10 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
@@ -1429,9 +1504,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.49.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.0.tgz",
|
||||
"integrity": "sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw==",
|
||||
"version": "1.49.8",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz",
|
||||
"integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
@@ -1442,7 +1517,7 @@
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.0"
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/select": {
|
||||
@@ -1911,9 +1986,9 @@
|
||||
}
|
||||
},
|
||||
"clipboard": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
|
||||
"integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.10.tgz",
|
||||
"integrity": "sha512-cz3m2YVwFz95qSEbCDi2fzLN/epEN9zXBvfgAoGkvGOJZATMl9gtTDVOtBYkx2ODUJl2kvmud7n32sV2BpYR4g==",
|
||||
"requires": {
|
||||
"good-listener": "^1.2.2",
|
||||
"select": "^1.1.2",
|
||||
@@ -1932,9 +2007,9 @@
|
||||
}
|
||||
},
|
||||
"codemirror": {
|
||||
"version": "5.65.1",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.1.tgz",
|
||||
"integrity": "sha512-s6aac+DD+4O2u1aBmdxhB7yz2XU7tG3snOyQ05Kxifahz7hoxnfxIRHxiCSEv3TUC38dIVH8G+lZH9UWSfGQxA=="
|
||||
"version": "5.65.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.2.tgz",
|
||||
"integrity": "sha512-SZM4Zq7XEC8Fhroqe3LxbEEX1zUPWH1wMr5zxiBuiUF64iYOUH/JI88v4tBag8MiBS8B8gRv8O1pPXGYXQ4ErA=="
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
@@ -2055,154 +2130,162 @@
|
||||
}
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.13.tgz",
|
||||
"integrity": "sha512-FIxvAdj3i2oHA6ex+E67bG7zlSTO+slt8kU2ogHDgGtrQLy2HNChv3PYjiFTYkt8hZbEAniZCXVeHn+FrHt7dA==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.23.tgz",
|
||||
"integrity": "sha512-XjnIcZ9KB6lfonCa+jRguXyRYcldmkyZ99ieDksqW/C8bnyEX299yA4QH2XcgijCgaddEZePPTgvx/2imsq7Ig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild-android-arm64": "0.14.13",
|
||||
"esbuild-darwin-64": "0.14.13",
|
||||
"esbuild-darwin-arm64": "0.14.13",
|
||||
"esbuild-freebsd-64": "0.14.13",
|
||||
"esbuild-freebsd-arm64": "0.14.13",
|
||||
"esbuild-linux-32": "0.14.13",
|
||||
"esbuild-linux-64": "0.14.13",
|
||||
"esbuild-linux-arm": "0.14.13",
|
||||
"esbuild-linux-arm64": "0.14.13",
|
||||
"esbuild-linux-mips64le": "0.14.13",
|
||||
"esbuild-linux-ppc64le": "0.14.13",
|
||||
"esbuild-linux-s390x": "0.14.13",
|
||||
"esbuild-netbsd-64": "0.14.13",
|
||||
"esbuild-openbsd-64": "0.14.13",
|
||||
"esbuild-sunos-64": "0.14.13",
|
||||
"esbuild-windows-32": "0.14.13",
|
||||
"esbuild-windows-64": "0.14.13",
|
||||
"esbuild-windows-arm64": "0.14.13"
|
||||
"esbuild-android-arm64": "0.14.23",
|
||||
"esbuild-darwin-64": "0.14.23",
|
||||
"esbuild-darwin-arm64": "0.14.23",
|
||||
"esbuild-freebsd-64": "0.14.23",
|
||||
"esbuild-freebsd-arm64": "0.14.23",
|
||||
"esbuild-linux-32": "0.14.23",
|
||||
"esbuild-linux-64": "0.14.23",
|
||||
"esbuild-linux-arm": "0.14.23",
|
||||
"esbuild-linux-arm64": "0.14.23",
|
||||
"esbuild-linux-mips64le": "0.14.23",
|
||||
"esbuild-linux-ppc64le": "0.14.23",
|
||||
"esbuild-linux-riscv64": "0.14.23",
|
||||
"esbuild-linux-s390x": "0.14.23",
|
||||
"esbuild-netbsd-64": "0.14.23",
|
||||
"esbuild-openbsd-64": "0.14.23",
|
||||
"esbuild-sunos-64": "0.14.23",
|
||||
"esbuild-windows-32": "0.14.23",
|
||||
"esbuild-windows-64": "0.14.23",
|
||||
"esbuild-windows-arm64": "0.14.23"
|
||||
}
|
||||
},
|
||||
"esbuild-android-arm64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.13.tgz",
|
||||
"integrity": "sha512-rhtwl+KJ3BzzXkK09N3/YbEF1i5WhriysJEStoeWNBzchx9hlmzyWmDGQQhu56HF78ua3JrVPyLOsdLGvtMvxQ==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-k9sXem++mINrZty1v4FVt6nC5BQCFG4K2geCIUUqHNlTdFnuvcqsY7prcKZLFhqVC1rbcJAr9VSUGFL/vD4vsw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.13.tgz",
|
||||
"integrity": "sha512-Fl47xIt5RMu50WIgMU93kwmUUJb+BPuL8R895n/aBNQqavS+KUMpLPoqKGABBV4myfx/fnAD/97X8Gt1C1YW6w==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.23.tgz",
|
||||
"integrity": "sha512-lB0XRbtOYYL1tLcYw8BoBaYsFYiR48RPrA0KfA/7RFTr4MV7Bwy/J4+7nLsVnv9FGuQummM3uJ93J3ptaTqFug==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-arm64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.13.tgz",
|
||||
"integrity": "sha512-UttqKRFXsWvuivcyAbFmo54vdkC9Me1ZYQNuoz/uBYDbkb2MgqKYG2+xoVKPBhLvhT0CKM5QGKD81flMH5BE6A==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-yat73Z/uJ5tRcfRiI4CCTv0FSnwErm3BJQeZAh+1tIP0TUNh6o+mXg338Zl5EKChD+YGp6PN+Dbhs7qa34RxSw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.13.tgz",
|
||||
"integrity": "sha512-dlIhPFSp29Yq2TPh7Cm3/4M0uKjlfvOylHVNCRvRNiOvDbBol6/NZ3kLisczms+Yra0rxVapBPN1oMbSMuts9g==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.23.tgz",
|
||||
"integrity": "sha512-/1xiTjoLuQ+LlbfjJdKkX45qK/M7ARrbLmyf7x3JhyQGMjcxRYVR6Dw81uH3qlMHwT4cfLW4aEVBhP1aNV7VsA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-arm64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.13.tgz",
|
||||
"integrity": "sha512-bNOHLu7Oq6RwaAMnwPbJ40DVGPl9GlAOnfH/dFZ792f8hFEbopkbtVzo1SU1jjfY3TGLWOgqHNWxPxx1N7Au+g==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-uyPqBU/Zcp6yEAZS4LKj5jEE0q2s4HmlMBIPzbW6cTunZ8cyvjG6YWpIZXb1KK3KTJDe62ltCrk3VzmWHp+iLg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-32": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.13.tgz",
|
||||
"integrity": "sha512-WzXyBx6zx16adGi7wPBvH2lRCBzYMcqnBRrJ8ciLIqYyruGvprZocX1nFWfiexjLcFxIElWnMNPX6LG7ULqyXA==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.23.tgz",
|
||||
"integrity": "sha512-37R/WMkQyUfNhbH7aJrr1uCjDVdnPeTHGeDhZPUNhfoHV0lQuZNCKuNnDvlH/u/nwIYZNdVvz1Igv5rY/zfrzQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.13.tgz",
|
||||
"integrity": "sha512-P6OFAfcoUvE7g9h/0UKm3qagvTovwqpCF1wbFLWe/BcCY8BS1bR/+SxUjCeKX2BcpIsg4/43ezHDE/ntg/iOpw==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.23.tgz",
|
||||
"integrity": "sha512-H0gztDP60qqr8zoFhAO64waoN5yBXkmYCElFklpd6LPoobtNGNnDe99xOQm28+fuD75YJ7GKHzp/MLCLhw2+vQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.13.tgz",
|
||||
"integrity": "sha512-4jmm0UySCg3Wi6FEBS7jpiPb1IyckI5um5kzYRwulHxPzkiokd6cgpcsTakR4/Y84UEicS8LnFAghHhXHZhbFg==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.23.tgz",
|
||||
"integrity": "sha512-x64CEUxi8+EzOAIpCUeuni0bZfzPw/65r8tC5cy5zOq9dY7ysOi5EVQHnzaxS+1NmV+/RVRpmrzGw1QgY2Xpmw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.13.tgz",
|
||||
"integrity": "sha512-k/uIvmkm4mc7vyMvJVwILgGxi2F+FuvLdmESIIWoHrnxEfEekC5AWpI/R6GQ2OMfp8snebSQLs8KL05QPnt1zA==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-c4MLOIByNHR55n3KoYf9hYDfBRghMjOiHLaoYLhkQkIabb452RWi+HsNgB41sUpSlOAqfpqKPFNg7VrxL3UX9g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-mips64le": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.13.tgz",
|
||||
"integrity": "sha512-vwYtgjQ1TRlUGL88km9wH9TjXsdZyZ/Xht1ASptg5XGRlqGquVjLGH11PfLLunoMdkQ0YTXR68b4l5gRfjVbyg==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.23.tgz",
|
||||
"integrity": "sha512-kHKyKRIAedYhKug2EJpyJxOUj3VYuamOVA1pY7EimoFPzaF3NeY7e4cFBAISC/Av0/tiV0xlFCt9q0HJ68IBIw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-ppc64le": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.13.tgz",
|
||||
"integrity": "sha512-0KqDSIkZaYugtcdpFCd3eQ38Fg6TzhxmOpkhDIKNTwD/W2RoXeiS+Z4y5yQ3oysb/ySDOxWkwNqTdXS4sz2LdQ==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.23.tgz",
|
||||
"integrity": "sha512-7ilAiJEPuJJnJp/LiDO0oJm5ygbBPzhchJJh9HsHZzeqO+3PUzItXi+8PuicY08r0AaaOe25LA7sGJ0MzbfBag==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-riscv64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.23.tgz",
|
||||
"integrity": "sha512-fbL3ggK2wY0D8I5raPIMPhpCvODFE+Bhb5QGtNP3r5aUsRR6TQV+ZBXIaw84iyvKC8vlXiA4fWLGhghAd/h/Zg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-s390x": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.13.tgz",
|
||||
"integrity": "sha512-bG20i7d0CN97fwPN9LaLe64E2IrI0fPZWEcoiff9hzzsvo/fQCx0YjMbPC2T3gqQ48QZRltdU9hQilTjHk3geQ==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.23.tgz",
|
||||
"integrity": "sha512-GHMDCyfy7+FaNSO8RJ8KCFsnax8fLUsOrj9q5Gi2JmZMY0Zhp75keb5abTFCq2/Oy6KVcT0Dcbyo/bFb4rIFJA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-netbsd-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.13.tgz",
|
||||
"integrity": "sha512-jz96PQb0ltqyqLggPpcRbWxzLvWHvrZBHZQyjcOzKRDqg1fR/R1y10b1Cuv84xoIbdAf+ceNUJkMN21FfR9G2g==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.23.tgz",
|
||||
"integrity": "sha512-ovk2EX+3rrO1M2lowJfgMb/JPN1VwVYrx0QPUyudxkxLYrWeBxDKQvc6ffO+kB4QlDyTfdtAURrVzu3JeNdA2g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-openbsd-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.13.tgz",
|
||||
"integrity": "sha512-bp6zSo3kDCXKPM5MmVUg6DEpt+yXDx37iDGzNTn3Kf9xh6d0cdITxUC4Bx6S3Di79GVYubWs+wNjSRVFIJpryw==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.23.tgz",
|
||||
"integrity": "sha512-uYYNqbVR+i7k8ojP/oIROAHO9lATLN7H2QeXKt2H310Fc8FJj4y3Wce6hx0VgnJ4k1JDrgbbiXM8rbEgQyg8KA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-sunos-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.13.tgz",
|
||||
"integrity": "sha512-08Fne1T9QHYxUnu55sV9V4i/yECADOaI1zMGET2YUa8SRkib10i80hc89U7U/G02DxpN/KUJMWEGq2wKTn0QFQ==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.23.tgz",
|
||||
"integrity": "sha512-hAzeBeET0+SbScknPzS2LBY6FVDpgE+CsHSpe6CEoR51PApdn2IB0SyJX7vGelXzlyrnorM4CAsRyb9Qev4h9g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-32": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.13.tgz",
|
||||
"integrity": "sha512-MW3BMIi9+fzTyDdljH0ftfT/qlD3t+aVzle1O+zZ2MgHRMQD20JwWgyqoJXhe6uDVyunrAUbcjH3qTIEZN3isg==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.23.tgz",
|
||||
"integrity": "sha512-Kttmi3JnohdaREbk6o9e25kieJR379TsEWF0l39PQVHXq3FR6sFKtVPgY8wk055o6IB+rllrzLnbqOw/UV60EA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.13.tgz",
|
||||
"integrity": "sha512-d7+0N+EOgBKdi/nMxlQ8QA5xHBlpcLtSrYnHsA+Xp4yZk28dYfRw1+embsHf5uN5/1iPvrJwPrcpgDH1xyy4JA==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.23.tgz",
|
||||
"integrity": "sha512-JtIT0t8ymkpl6YlmOl6zoSWL5cnCgyLaBdf/SiU/Eg3C13r0NbHZWNT/RDEMKK91Y6t79kTs3vyRcNZbfu5a8g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-arm64": {
|
||||
"version": "0.14.13",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.13.tgz",
|
||||
"integrity": "sha512-oX5hmgXk9yNKbb5AxThzRQm/E9kiHyDll7JJeyeT1fuGENTifv33f0INCpjBQ+Ty5ChKc84++ZQTEBwLCA12Kw==",
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-cTFaQqT2+ik9e4hePvYtRZQ3pqOvKDVNarzql0VFIzhc0tru/ZgdLoXd6epLiKT+SzoSce6V9YJ+nn6RCn6SHw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@@ -2803,9 +2886,9 @@
|
||||
}
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.49.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.0.tgz",
|
||||
"integrity": "sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw==",
|
||||
"version": "1.49.8",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz",
|
||||
"integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
|
||||
12
package.json
@@ -4,9 +4,9 @@
|
||||
"build:css:dev": "sass ./resources/sass:./public/dist",
|
||||
"build:css:watch": "sass ./resources/sass:./public/dist --watch",
|
||||
"build:css:production": "sass ./resources/sass:./public/dist -s compressed",
|
||||
"build:js:dev": "esbuild --bundle ./resources/js/index.js --outfile=public/dist/app.js --sourcemap --target=es2019 --main-fields=module,main",
|
||||
"build:js:dev": "esbuild --bundle ./resources/js/*.{js,mjs} --outdir=public/dist/ --sourcemap --target=es2020 --main-fields=module,main --format=esm",
|
||||
"build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"",
|
||||
"build:js:production": "NODE_ENV=production esbuild --bundle ./resources/js/index.js --outfile=public/dist/app.js --sourcemap --target=es2019 --main-fields=module,main --minify",
|
||||
"build:js:production": "NODE_ENV=production esbuild --bundle ./resources/js/*.{js,mjs} --outdir=public/dist/ --sourcemap --target=es2020 --main-fields=module,main --minify --format=esm",
|
||||
"build": "npm-run-all --parallel build:*:dev",
|
||||
"production": "npm-run-all --parallel build:*:production",
|
||||
"dev": "npm-run-all --parallel watch livereload",
|
||||
@@ -16,15 +16,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"chokidar-cli": "^3.0",
|
||||
"esbuild": "0.14.13",
|
||||
"esbuild": "0.14.23",
|
||||
"livereload": "^0.9.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"punycode": "^2.1.1",
|
||||
"sass": "^1.49.0"
|
||||
"sass": "^1.49.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"clipboard": "^2.0.8",
|
||||
"codemirror": "^5.65.1",
|
||||
"clipboard": "^2.0.10",
|
||||
"codemirror": "^5.65.2",
|
||||
"dropzone": "^5.9.3",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
|
||||
100
public/dist/app.js
vendored
33
public/dist/code.js
vendored
Normal file
2
public/dist/export-styles.css
vendored
2
public/dist/styles.css
vendored
1
public/libs/tinymce/icons/default/icons.min.js
vendored
Normal file
@@ -1 +1,9 @@
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.util.Tools"),s=function(t,e,n){var r="UL"===e?"InsertUnorderedList":"InsertOrderedList";t.execCommand(r,!1,!1===n?null:{"list-style-type":n})},o=function(n){n.addCommand("ApplyUnorderedListStyle",function(t,e){s(n,"UL",e["list-style-type"])}),n.addCommand("ApplyOrderedListStyle",function(t,e){s(n,"OL",e["list-style-type"])})},e=function(t){var e=t.getParam("advlist_number_styles","default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman");return e?e.split(/[ ,]/):[]},n=function(t){var e=t.getParam("advlist_bullet_styles","default,circle,disc,square");return e?e.split(/[ ,]/):[]},u=function(t){return t&&/^(TH|TD)$/.test(t.nodeName)},c=function(r){return function(t){return t&&/^(OL|UL|DL)$/.test(t.nodeName)&&(n=t,(e=r).$.contains(e.getBody(),n));var e,n}},d=function(t){var e=t.dom.getParent(t.selection.getNode(),"ol,ul");return t.dom.getStyle(e,"listStyleType")||""},p=function(t){return a.map(t,function(t){return{text:t.replace(/\-/g," ").replace(/\b\w/g,function(t){return t.toUpperCase()}),data:"default"===t?"":t}})},f=function(i,l){return function(t){var o=t.control;i.on("NodeChange",function(t){var e=function(t,e){for(var n=0;n<t.length;n++)if(e(t[n]))return n;return-1}(t.parents,u),n=-1!==e?t.parents.slice(0,e):t.parents,r=a.grep(n,c(i));o.active(0<r.length&&r[0].nodeName===l)})}},m=function(e,t,n,r,o,i){var l;e.addButton(t,{active:!1,type:"splitbutton",tooltip:n,menu:p(i),onPostRender:f(e,o),onshow:(l=e,function(t){var e=d(l);t.control.items().each(function(t){t.active(t.settings.data===e)})}),onselect:function(t){s(e,o,t.control.settings.data)},onclick:function(){e.execCommand(r)}})},r=function(t,e,n,r,o,i){var l,a,s,u,c;0<i.length?m(t,e,n,r,o,i):(a=e,s=n,u=r,c=o,(l=t).addButton(a,{active:!1,type:"button",tooltip:s,onPostRender:f(l,c),onclick:function(){l.execCommand(u)}}))},i=function(t){r(t,"numlist","Numbered list","InsertOrderedList","OL",e(t)),r(t,"bullist","Bullet list","InsertUnorderedList","UL",n(t))};t.add("advlist",function(t){var e,n,r;n="lists",r=(e=t).settings.plugins?e.settings.plugins:"",-1!==a.inArray(r.split(/[ ,]/),n)&&(i(t),o(t))})}();
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*
|
||||
* Version: 5.10.2 (2021-11-17)
|
||||
*/
|
||||
!function(){"use strict";function f(t,e,n){t.execCommand("UL"===e?"InsertUnorderedList":"InsertOrderedList",!1,!1===n?null:{"list-style-type":n})}function i(t){return function(){return t}}function t(t){return t}function e(){return s}var n=tinymce.util.Tools.resolve("tinymce.PluginManager"),g=tinymce.util.Tools.resolve("tinymce.util.Tools"),u=i(!1),l=i(!0),s={fold:function(t,e){return t()},isSome:u,isNone:l,getOr:t,getOrThunk:r,getOrDie:function(t){throw new Error(t||"error: getOrDie called on none.")},getOrNull:i(null),getOrUndefined:i(void 0),or:t,orThunk:r,map:e,each:function(){},bind:e,exists:u,forall:l,filter:function(){return s},toArray:function(){return[]},toString:i("none()")};function r(t){return t()}function d(t){return t&&/^(TH|TD)$/.test(t.nodeName)}function m(c,a){return function(s){function t(t){var e,n,r,o,i,u,l;s.setActive((e=c,r=a,i=-1!==(o=function(t,e){for(var n=0;n<t.length;n++)if(e(t[n]))return n;return-1}((n=t).parents,d))?n.parents.slice(0,o):n.parents,0<(u=g.grep(i,(l=e,function(t){return t&&/^(OL|UL|DL)$/.test(t.nodeName)&&(e=t,l.$.contains(l.getBody(),e));var e}))).length&&u[0].nodeName===r))}return c.on("NodeChange",t),function(){return c.off("NodeChange",t)}}}function c(t,e,n,r,o,i){var u,l,s,c,a,d;1<i.length?(c=r,a=o,d=i,(s=t).ui.registry.addSplitButton(e,{tooltip:n,icon:"OL"===a?"ordered-list":"unordered-list",presets:"listpreview",columns:3,fetch:function(t){t(g.map(d,function(t){return{type:"choiceitem",value:"default"===t?"":t,icon:"list-"+("OL"===a?"num":"bull")+"-"+("disc"===t||"decimal"===t?"default":t),text:t.replace(/\-/g," ").replace(/\b\w/g,function(t){return t.toUpperCase()})}}))},onAction:function(){return s.execCommand(c)},onItemAction:function(t,e){f(s,a,e)},select:function(e){var t,n=(t=s).dom.getParent(t.selection.getNode(),"ol,ul"),r=t.dom.getStyle(n,"listStyleType");return p(r).map(function(t){return e===t}).getOr(!1)},onSetup:m(s,a)})):(l=r,(u=t).ui.registry.addToggleButton(e,{active:!1,tooltip:n,icon:"OL"===o?"ordered-list":"unordered-list",onSetup:m(u,o),onAction:function(){return u.execCommand(l)}}))}var a=function(n){function t(){return o}function e(t){return t(n)}var r=i(n),o={fold:function(t,e){return e(n)},isSome:l,isNone:u,getOr:r,getOrThunk:r,getOrDie:r,getOrNull:r,getOrUndefined:r,or:t,orThunk:t,map:function(t){return a(t(n))},each:function(t){t(n)},bind:e,exists:e,forall:e,filter:function(t){return t(n)?o:s},toArray:function(){return[n]},toString:function(){return"some("+n+")"}};return o},p=function(t){return null==t?s:a(t)};n.add("advlist",function(t){var n,e,r,o;t.hasPlugin("lists")?(c(e=t,"numlist","Numbered list","InsertOrderedList","OL",(r=e.getParam("advlist_number_styles","default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman"))?r.split(/[ ,]/):[]),c(e,"bullist","Bullet list","InsertUnorderedList","UL",(o=e.getParam("advlist_bullet_styles","default,circle,square"))?o.split(/[ ,]/):[]),(n=t).addCommand("ApplyUnorderedListStyle",function(t,e){f(n,"UL",e["list-style-type"])}),n.addCommand("ApplyOrderedListStyle",function(t,e){f(n,"OL",e["list-style-type"])})):console.error("Please use the Lists plugin together with the Advanced List plugin.")})}();
|
||||
10
public/libs/tinymce/plugins/anchor/plugin.min.js
vendored
@@ -1 +1,9 @@
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=function(t){return/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)},e=function(t){var e=t.selection.getNode();return"A"===e.tagName&&""===t.dom.getAttrib(e,"href")?e.id||e.name:""},i=function(t,e){var n=t.selection.getNode();"A"===n.tagName&&""===t.dom.getAttrib(n,"href")?(n.removeAttribute("name"),n.id=e,t.undoManager.add()):(t.focus(),t.selection.collapse(!0),t.execCommand("mceInsertContent",!1,t.dom.createHTML("a",{id:e})))},n=function(r){var t=e(r);r.windowManager.open({title:"Anchor",body:{type:"textbox",name:"id",size:40,label:"Id",value:t},onsubmit:function(t){var e,n,o=t.data.id;e=r,(a(n=o)?(i(e,n),0):(e.windowManager.alert("Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),1))&&t.preventDefault()}})},o=function(t){t.addCommand("mceAnchor",function(){n(t)})},r=function(o){return function(t){for(var e=0;e<t.length;e++)(n=t[e]).attr("href")||!n.attr("id")&&!n.attr("name")||n.firstChild||t[e].attr("contenteditable",o);var n}},c=function(t){t.on("PreInit",function(){t.parser.addNodeFilter("a",r("false")),t.serializer.addNodeFilter("a",r(null))})},d=function(t){t.addButton("anchor",{icon:"anchor",tooltip:"Anchor",cmd:"mceAnchor",stateSelector:"a:not([href])"}),t.addMenuItem("anchor",{icon:"anchor",text:"Anchor",context:"insert",cmd:"mceAnchor"})};t.add("anchor",function(t){c(t),o(t),d(t)})}();
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*
|
||||
* Version: 5.10.2 (2021-11-17)
|
||||
*/
|
||||
!function(){"use strict";function a(e){return e.getAttribute("id")||e.getAttribute("name")||""}function c(e){return e&&"a"===e.nodeName.toLowerCase()&&!e.getAttribute("href")&&""!==a(e)}function d(e){return e.dom.getParent(e.selection.getStart(),l)}function r(e,t){var o,a,n,r,i,l=d(e);l?(n=e,r=t,(i=l).removeAttribute("name"),i.id=r,n.addVisual(),n.undoManager.add()):(a=t,(o=e).undoManager.transact(function(){var e,n;o.getParam("allow_html_in_named_anchor",!1,"boolean")||o.selection.collapse(!0),o.selection.isCollapsed()?o.insertContent(o.dom.createHTML("a",{id:a})):(n=(e=o).dom,u(n).walk(e.selection.getRng(),function(e){s.each(e,function(e){var t;c(t=e)&&!t.firstChild&&n.remove(e,!1)})}),o.formatter.remove("namedAnchor",null,null,!0),o.formatter.apply("namedAnchor",{value:a}),o.addVisual())})),e.focus()}function i(r){return function(e){for(var t,n=0;n<e.length;n++){var o=e[n],a=void 0;!(a=t=o)||a.attr("href")||!a.attr("id")&&!a.attr("name")||t.firstChild||o.attr("contenteditable",r)}}}var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),u=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),s=tinymce.util.Tools.resolve("tinymce.util.Tools"),l="a:not([href])";e.add("anchor",function(e){var t,n,o;(t=e).on("PreInit",function(){t.parser.addNodeFilter("a",i("false")),t.serializer.addNodeFilter("a",i(null))}),(n=e).addCommand("mceAnchor",function(){var o,e,t;t=(e=d(o=n))?a(e):"",o.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:t},onSubmit:function(e){var t=o,n=e.getData().id;(/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(n)?(r(t,n),0):(t.windowManager.alert("Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),1))||e.close()}})}),(o=e).ui.registry.addToggleButton("anchor",{icon:"bookmark",tooltip:"Anchor",onAction:function(){return o.execCommand("mceAnchor")},onSetup:function(e){return o.selection.selectorChangedWithUnbind("a:not([href])",e.setActive).unbind}}),o.ui.registry.addMenuItem("anchor",{icon:"bookmark",text:"Anchor...",onAction:function(){return o.execCommand("mceAnchor")}}),e.on("PreInit",function(){e.formatter.register("namedAnchor",{inline:"a",selector:l,remove:"all",split:!0,deep:!0,attributes:{id:"%value"},onmatch:c})})})}();
|
||||
@@ -1 +1,9 @@
|
||||
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),i=tinymce.util.Tools.resolve("tinymce.Env"),m=function(e){return e.getParam("autolink_pattern",/^(https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.|(?:mailto:)?[A-Z0-9._%+\-]+@)(.+)$/i)},y=function(e){return e.getParam("default_link_target","")},o=function(e,t){if(t<0&&(t=0),3===e.nodeType){var n=e.data.length;n<t&&(t=n)}return t},k=function(e,t,n){1!==t.nodeType||t.hasChildNodes()?e.setStart(t,o(t,n)):e.setStartBefore(t)},p=function(e,t,n){1!==t.nodeType||t.hasChildNodes()?e.setEnd(t,o(t,n)):e.setEndAfter(t)},r=function(e,t,n){var i,o,r,a,f,s,d,l,c,u,g=m(e),h=y(e);if("A"!==e.selection.getNode().tagName){if((i=e.selection.getRng(!0).cloneRange()).startOffset<5){if(!(l=i.endContainer.previousSibling)){if(!i.endContainer.firstChild||!i.endContainer.firstChild.nextSibling)return;l=i.endContainer.firstChild.nextSibling}if(c=l.length,k(i,l,c),p(i,l,c),i.endOffset<5)return;o=i.endOffset,a=l}else{if(3!==(a=i.endContainer).nodeType&&a.firstChild){for(;3!==a.nodeType&&a.firstChild;)a=a.firstChild;3===a.nodeType&&(k(i,a,0),p(i,a,a.nodeValue.length))}o=1===i.endOffset?2:i.endOffset-1-t}for(r=o;k(i,a,2<=o?o-2:0),p(i,a,1<=o?o-1:0),o-=1," "!==(u=i.toString())&&""!==u&&160!==u.charCodeAt(0)&&0<=o-2&&u!==n;);var C;(C=i.toString())===n||" "===C||160===C.charCodeAt(0)?(k(i,a,o),p(i,a,r),o+=1):(0===i.startOffset?k(i,a,0):k(i,a,o),p(i,a,r)),"."===(s=i.toString()).charAt(s.length-1)&&p(i,a,r-1),(d=(s=i.toString().trim()).match(g))&&("www."===d[1]?d[1]="http://www.":/@$/.test(d[1])&&!/^mailto:/.test(d[1])&&(d[1]="mailto:"+d[1]),f=e.selection.getBookmark(),e.selection.setRng(i),e.execCommand("createlink",!1,d[1]+d[2]),h&&e.dom.setAttrib(e.selection.getNode(),"target",h),e.selection.moveToBookmark(f),e.nodeChanged())}},t=function(t){var n;t.on("keydown",function(e){13!==e.keyCode||r(t,-1,"")}),i.ie?t.on("focus",function(){if(!n){n=!0;try{t.execCommand("AutoUrlDetect",!1,!0)}catch(e){}}}):(t.on("keypress",function(e){41!==e.keyCode||r(t,-1,"(")}),t.on("keyup",function(e){32!==e.keyCode||r(t,0,"")}))};e.add("autolink",function(e){t(e)})}();
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*
|
||||
* Version: 5.10.2 (2021-11-17)
|
||||
*/
|
||||
!function(){"use strict";function k(e){return/^[(\[{ \u00a0]$/.test(e)}function w(e){return 3===e.nodeType}function i(e){return 1===e.nodeType}function o(e,t){var n;return t<0&&(t=0),!w(e)||(n=e.data.length)<t&&(t=n),t}function y(e,t,n){!i(t)||t.hasChildNodes()?e.setStart(t,o(t,n)):e.setStartBefore(t)}function v(e,t,n){!i(t)||t.hasChildNodes()?e.setEnd(t,o(t,n)):e.setEndAfter(t)}function r(e,t){var n,i,o,r,a,f=e.getParam("autolink_pattern",A),s=e.getParam("default_link_target",!1);if(null===e.dom.getParent(e.selection.getNode(),"a[href]")){var d=e.selection.getRng().cloneRange();if(d.startOffset<5){if(!(r=d.endContainer.previousSibling)){if(!d.endContainer.firstChild||!d.endContainer.firstChild.nextSibling)return;r=d.endContainer.firstChild.nextSibling}if(y(d,r,a=r.length),v(d,r,a),d.endOffset<5)return;n=d.endOffset,i=r}else{if(!w(i=d.endContainer)&&i.firstChild){for(;!w(i)&&i.firstChild;)i=i.firstChild;w(i)&&(y(d,i,0),v(d,i,i.nodeValue.length))}n=1===d.endOffset?2:d.endOffset-1-t}for(var l=n;y(d,i,2<=n?n-2:0),v(d,i,1<=n?n-1:0),--n,!k(d.toString())&&0<=n-2;);k(d.toString())?(y(d,i,n),v(d,i,l),n+=1):(0===d.startOffset?y(d,i,0):y(d,i,n),v(d,i,l)),u=d.toString(),/[?!,.;:]/.test(u.charAt(u.length-1))&&v(d,i,l-1);var u,c,g,h,C=(u=d.toString().trim()).match(f),m=e.getParam("link_default_protocol","http","string");C&&((g=c=C[0]).length>=(h="www.").length&&g.substr(0,0+h.length)===h?c=m+"://"+c:-1===c.indexOf("@")||/^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(c)||(c="mailto:"+c),o=e.selection.getBookmark(),e.selection.setRng(d),e.execCommand("createlink",!1,c),!1!==s&&e.dom.setAttrib(e.selection.getNode(),"target",s),e.selection.moveToBookmark(o),e.nodeChanged())}}var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env"),A=new RegExp("^"+/(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)[A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*(?::\d+)?(?:\/(?:[-+~=.,%()\/\w]*[-+~=%()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?/g.source+"$","i");e.add("autolink",function(e){var t,n;(t=e).on("keydown",function(e){if(13===e.keyCode)return r(t,-1)}),a.browser.isIE()?t.on("focus",function(){if(!n){n=!0;try{t.execCommand("AutoUrlDetect",!1,!0)}catch(e){}}}):(t.on("keypress",function(e){if(41===e.keyCode||93===e.keyCode||125===e.keyCode)return r(t,-1)}),t.on("keyup",function(e){if(32===e.keyCode)return r(t,0)}))})}();
|
||||
@@ -1 +1,9 @@
|
||||
!function(){"use strict";var i=function(t){var e=t,n=function(){return e};return{get:n,set:function(t){e=t},clone:function(){return i(n())}}},t=tinymce.util.Tools.resolve("tinymce.PluginManager"),y=tinymce.util.Tools.resolve("tinymce.Env"),r=tinymce.util.Tools.resolve("tinymce.util.Delay"),h=function(t){return parseInt(t.getParam("autoresize_min_height",t.getElement().offsetHeight),10)},v=function(t){return parseInt(t.getParam("autoresize_max_height",0),10)},o=function(t){return t.getParam("autoresize_overflow_padding",1)},a=function(t){return t.getParam("autoresize_bottom_margin",50)},n=function(t){return t.getParam("autoresize_on_init",!0)},u=function(t,e,n,i,o){r.setEditorTimeout(t,function(){_(t,e),n--?u(t,e,n,i,o):o&&o()},i)},S=function(t,e){var n=t.getBody();n&&(n.style.overflowY=e?"":"hidden",e||(n.scrollTop=0))},_=function(t,e){var n,i,o,r,a,u,s,l,g,c,f,d=t.dom;if(i=t.getDoc())if((m=t).plugins.fullscreen&&m.plugins.fullscreen.isFullscreen())S(t,!0);else{var m;o=i.body,r=h(t),u=d.getStyle(o,"margin-top",!0),s=d.getStyle(o,"margin-bottom",!0),l=d.getStyle(o,"padding-top",!0),g=d.getStyle(o,"padding-bottom",!0),c=d.getStyle(o,"border-top-width",!0),f=d.getStyle(o,"border-bottom-width",!0),a=o.offsetHeight+parseInt(u,10)+parseInt(s,10)+parseInt(l,10)+parseInt(g,10)+parseInt(c,10)+parseInt(f,10),(isNaN(a)||a<=0)&&(a=y.ie?o.scrollHeight:y.webkit&&0===o.clientHeight?0:o.offsetHeight),a>h(t)&&(r=a);var p=v(t);p&&p<a?(r=p,S(t,!0)):S(t,!1),r!==e.get()&&(n=r-e.get(),d.setStyle(t.iframeElement,"height",r+"px"),e.set(r),y.webkit&&n<0&&_(t,e))}},s={setup:function(i,e){i.on("init",function(){var t,e,n=i.dom;t=o(i),e=a(i),!1!==t&&n.setStyles(i.getBody(),{paddingLeft:t,paddingRight:t}),!1!==e&&n.setStyles(i.getBody(),{paddingBottom:e})}),i.on("nodechange setcontent keyup FullscreenStateChanged",function(t){_(i,e)}),n(i)&&i.on("init",function(){u(i,e,20,100,function(){u(i,e,5,1e3)})})},resize:_},l=function(t,e){t.addCommand("mceAutoResize",function(){s.resize(t,e)})};t.add("autoresize",function(t){if(!t.inline){var e=i(0);l(t,e),s.setup(t,e)}})}();
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*
|
||||
* Version: 5.10.2 (2021-11-17)
|
||||
*/
|
||||
!function(){"use strict";function y(e){return e.getParam("min_height",e.getElement().offsetHeight,"number")}function p(e,t){var n=e.getBody();n&&(n.style.overflowY=t?"":"hidden",t||(n.scrollTop=0))}function v(e,t,n,i){var o=parseInt(e.getStyle(t,n,i),10);return isNaN(o)?0:o}var l=Object.hasOwnProperty,e=tinymce.util.Tools.resolve("tinymce.PluginManager"),b=tinymce.util.Tools.resolve("tinymce.Env"),r=tinymce.util.Tools.resolve("tinymce.util.Delay"),u=function(e,t,n,i,o){r.setEditorTimeout(e,function(){C(e,t),n--?u(e,t,n,i,o):o&&o()},i)},C=function(e,t,n){var i,o,r,s,a,l,u,g,c,m,f,d=e.dom,h=e.getDoc();h&&(e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen()?p(e,!0):(i=h.documentElement,o=e.getParam("autoresize_bottom_margin",50,"number"),r=y(e),s=v(d,i,"margin-top",!0),a=v(d,i,"margin-bottom",!0),(l=(l=i.offsetHeight+s+a+o)<0?0:l)+(u=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight)>y(e)&&(r=l+u),(g=e.getParam("max_height",0,"number"))&&g<r?(r=g,p(e,!0)):p(e,!1),r!==t.get()&&(c=r-t.get(),d.setStyle(e.getContainer(),"height",r+"px"),t.set(r),e.fire("ResizeEditor"),b.browser.isSafari()&&b.mac&&(m=e.getWin()).scrollTo(m.pageXOffset,m.pageYOffset),!e.hasFocus()||"setcontent"!==(null==(f=n)?void 0:f.type.toLowerCase())||!0!==f.selection&&!0!==f.paste||e.selection.scrollIntoView(),b.webkit&&c<0&&C(e,t,n))))};e.add("autoresize",function(e){var t,n,i,o,r,s,a=e.settings;l.call(a,"resize")||(e.settings.resize=!1),e.inline||(s=0,r=t={get:function(){return s},set:function(e){s=e}},(o=e).addCommand("mceAutoResize",function(){C(o,r)}),i=t,(n=e).on("init",function(){var e=n.getParam("autoresize_overflow_padding",1,"number"),t=n.dom;t.setStyles(n.getDoc().documentElement,{height:"auto"}),t.setStyles(n.getBody(),{paddingLeft:e,paddingRight:e,"min-height":0})}),n.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",function(e){C(n,i,e)}),n.getParam("autoresize_on_init",!0,"boolean")&&n.on("init",function(){u(n,i,20,100,function(){u(n,i,5,1e3)})}))})}();
|
||||
@@ -1 +1,9 @@
|
||||
!function(a){"use strict";var i=function(t){var e=t,n=function(){return e};return{get:n,set:function(t){e=t},clone:function(){return i(n())}}},t=tinymce.util.Tools.resolve("tinymce.PluginManager"),r=tinymce.util.Tools.resolve("tinymce.util.LocalStorage"),o=tinymce.util.Tools.resolve("tinymce.util.Tools"),u=function(t,e){var n=t||e,r=/^(\d+)([ms]?)$/.exec(""+n);return(r[2]?{s:1e3,m:6e4}[r[2]]:1)*parseInt(n,10)},s=function(t){var e=t.getParam("autosave_prefix","tinymce-autosave-{path}{query}{hash}-{id}-");return e=(e=(e=(e=e.replace(/\{path\}/g,a.document.location.pathname)).replace(/\{query\}/g,a.document.location.search)).replace(/\{hash\}/g,a.document.location.hash)).replace(/\{id\}/g,t.id)},c=function(t,e){var n=t.settings.forced_root_block;return""===(e=o.trim(void 0===e?t.getBody().innerHTML:e))||new RegExp("^<"+n+"[^>]*>((\xa0| |[ \t]|<br[^>]*>)+?|)</"+n+">|<br>$","i").test(e)},f=function(t){var e=parseInt(r.getItem(s(t)+"time"),10)||0;return!((new Date).getTime()-e>u(t.settings.autosave_retention,"20m")&&(l(t,!1),1))},l=function(t,e){var n=s(t);r.removeItem(n+"draft"),r.removeItem(n+"time"),!1!==e&&t.fire("RemoveDraft")},m=function(t){var e=s(t);!c(t)&&t.isDirty()&&(r.setItem(e+"draft",t.getContent({format:"raw",no_events:!0})),r.setItem(e+"time",(new Date).getTime().toString()),t.fire("StoreDraft"))},v=function(t){var e=s(t);f(t)&&(t.setContent(r.getItem(e+"draft"),{format:"raw"}),t.fire("RestoreDraft"))},d=function(t,e){var n=u(t.settings.autosave_interval,"30s");e.get()||(setInterval(function(){t.removed||m(t)},n),e.set(!0))},g=function(t){t.undoManager.transact(function(){v(t),l(t)}),t.focus()};function y(r){for(var o=[],t=1;t<arguments.length;t++)o[t-1]=arguments[t];return function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];var n=o.concat(t);return r.apply(null,n)}}var p=tinymce.util.Tools.resolve("tinymce.EditorManager");p._beforeUnloadHandler=function(){var e;return o.each(p.get(),function(t){t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&t.getParam("autosave_ask_before_unload",!0)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))}),e};var h=function(n,r){return function(t){var e=t.control;e.disabled(!f(n)),n.on("StoreDraft RestoreDraft RemoveDraft",function(){e.disabled(!f(n))}),d(n,r)}};t.add("autosave",function(t){var e,n,r,o=i(!1);return a.window.onbeforeunload=p._beforeUnloadHandler,n=o,(e=t).addButton("restoredraft",{title:"Restore last draft",onclick:function(){g(e)},onPostRender:h(e,n)}),e.addMenuItem("restoredraft",{text:"Restore last draft",onclick:function(){g(e)},onPostRender:h(e,n),context:"file"}),t.on("init",function(){t.getParam("autosave_restore_when_empty",!1)&&t.dom.isEmpty(t.getBody())&&v(t)}),{hasDraft:y(f,r=t),storeDraft:y(m,r),restoreDraft:y(v,r),removeDraft:y(l,r),isEmpty:y(c,r)}})}(window);
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*
|
||||
* Version: 5.10.2 (2021-11-17)
|
||||
*/
|
||||
!function(){"use strict";function o(t,e){var r=t||e,n=/^(\d+)([ms]?)$/.exec(""+r);return(n[2]?{s:1e3,m:6e4}[n[2]]:1)*parseInt(r,10)}function n(t){var e=document.location;return t.getParam("autosave_prefix","tinymce-autosave-{path}{query}{hash}-{id}-").replace(/{path}/g,e.pathname).replace(/{query}/g,e.search).replace(/{hash}/g,e.hash).replace(/{id}/g,t.id)}function i(t,e){if(a(e))return t.dom.isEmpty(t.getBody());var r=d.trim(e);if(""===r)return!0;var n=(new DOMParser).parseFromString(r,"text/html");return t.dom.isEmpty(n)}function u(t){var e=parseInt(v.getItem(n(t)+"time"),10)||0;return!((new Date).getTime()-e>o(t.getParam("autosave_retention"),"20m")&&(g(t,!1),1))}function s(t){var e=n(t);!i(t)&&t.isDirty()&&(v.setItem(e+"draft",t.getContent({format:"raw",no_events:!0})),v.setItem(e+"time",(new Date).getTime().toString()),t.fire("StoreDraft"))}function f(t){var e=n(t);u(t)&&(t.setContent(v.getItem(e+"draft"),{format:"raw"}),t.fire("RestoreDraft"))}function c(t){t.undoManager.transact(function(){f(t),g(t)}),t.focus()}function m(r){return function(t){function e(){return t.setDisabled(!u(r))}return t.setDisabled(!u(r)),r.on("StoreDraft RestoreDraft RemoveDraft",e),function(){return r.off("StoreDraft RestoreDraft RemoveDraft",e)}}}var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=function(t){return void 0===t},l=tinymce.util.Tools.resolve("tinymce.util.Delay"),v=tinymce.util.Tools.resolve("tinymce.util.LocalStorage"),d=tinymce.util.Tools.resolve("tinymce.util.Tools"),g=function(t,e){var r=n(t);v.removeItem(r+"draft"),v.removeItem(r+"time"),!1!==e&&t.fire("RemoveDraft")},y=tinymce.util.Tools.resolve("tinymce.EditorManager");t.add("autosave",function(t){var e,r,n,a;return t.editorManager.on("BeforeUnload",function(t){var e;d.each(y.get(),function(t){t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&t.getParam("autosave_ask_before_unload",!0)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))}),e&&(t.preventDefault(),t.returnValue=e)}),n=e=t,a=o(n.getParam("autosave_interval"),"30s"),l.setEditorInterval(n,function(){s(n)},a),e.ui.registry.addButton("restoredraft",{tooltip:"Restore last draft",icon:"restore-draft",onAction:function(){c(e)},onSetup:m(e)}),e.ui.registry.addMenuItem("restoredraft",{text:"Restore last draft",icon:"restore-draft",onAction:function(){c(e)},onSetup:m(e)}),t.on("init",function(){t.getParam("autosave_restore_when_empty",!1)&&t.dom.isEmpty(t.getBody())&&f(t)}),r=t,{hasDraft:function(){return u(r)},storeDraft:function(){return s(r)},restoreDraft:function(){return f(r)},removeDraft:function(t){return g(r,t)},isEmpty:function(t){return i(r,t)}}})}();
|
||||
10
public/libs/tinymce/plugins/bbcode/plugin.min.js
vendored
@@ -1 +1,9 @@
|
||||
!function(){"use strict";var o=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.util.Tools"),e=function(e){e=t.trim(e);var o=function(o,t){e=e.replace(o,t)};return o(/<a.*?href=\"(.*?)\".*?>(.*?)<\/a>/gi,"[url=$1]$2[/url]"),o(/<font.*?color=\"(.*?)\".*?class=\"codeStyle\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),o(/<font.*?color=\"(.*?)\".*?class=\"quoteStyle\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),o(/<font.*?class=\"codeStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),o(/<font.*?class=\"quoteStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),o(/<span style=\"color: ?(.*?);\">(.*?)<\/span>/gi,"[color=$1]$2[/color]"),o(/<font.*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[color=$1]$2[/color]"),o(/<span style=\"font-size:(.*?);\">(.*?)<\/span>/gi,"[size=$1]$2[/size]"),o(/<font>(.*?)<\/font>/gi,"$1"),o(/<img.*?src=\"(.*?)\".*?\/>/gi,"[img]$1[/img]"),o(/<span class=\"codeStyle\">(.*?)<\/span>/gi,"[code]$1[/code]"),o(/<span class=\"quoteStyle\">(.*?)<\/span>/gi,"[quote]$1[/quote]"),o(/<strong class=\"codeStyle\">(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]"),o(/<strong class=\"quoteStyle\">(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]"),o(/<em class=\"codeStyle\">(.*?)<\/em>/gi,"[code][i]$1[/i][/code]"),o(/<em class=\"quoteStyle\">(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]"),o(/<u class=\"codeStyle\">(.*?)<\/u>/gi,"[code][u]$1[/u][/code]"),o(/<u class=\"quoteStyle\">(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]"),o(/<\/(strong|b)>/gi,"[/b]"),o(/<(strong|b)>/gi,"[b]"),o(/<\/(em|i)>/gi,"[/i]"),o(/<(em|i)>/gi,"[i]"),o(/<\/u>/gi,"[/u]"),o(/<span style=\"text-decoration: ?underline;\">(.*?)<\/span>/gi,"[u]$1[/u]"),o(/<u>/gi,"[u]"),o(/<blockquote[^>]*>/gi,"[quote]"),o(/<\/blockquote>/gi,"[/quote]"),o(/<br \/>/gi,"\n"),o(/<br\/>/gi,"\n"),o(/<br>/gi,"\n"),o(/<p>/gi,""),o(/<\/p>/gi,"\n"),o(/ |\u00a0/gi," "),o(/"/gi,'"'),o(/</gi,"<"),o(/>/gi,">"),o(/&/gi,"&"),e},i=function(e){e=t.trim(e);var o=function(o,t){e=e.replace(o,t)};return o(/\n/gi,"<br />"),o(/\[b\]/gi,"<strong>"),o(/\[\/b\]/gi,"</strong>"),o(/\[i\]/gi,"<em>"),o(/\[\/i\]/gi,"</em>"),o(/\[u\]/gi,"<u>"),o(/\[\/u\]/gi,"</u>"),o(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,'<a href="$1">$2</a>'),o(/\[url\](.*?)\[\/url\]/gi,'<a href="$1">$1</a>'),o(/\[img\](.*?)\[\/img\]/gi,'<img src="$1" />'),o(/\[color=(.*?)\](.*?)\[\/color\]/gi,'<font color="$1">$2</font>'),o(/\[code\](.*?)\[\/code\]/gi,'<span class="codeStyle">$1</span> '),o(/\[quote.*?\](.*?)\[\/quote\]/gi,'<span class="quoteStyle">$1</span> '),e};o.add("bbcode",function(){return{init:function(o){o.on("beforeSetContent",function(o){o.content=i(o.content)}),o.on("postProcess",function(o){o.set&&(o.content=i(o.content)),o.get&&(o.content=e(o.content))})}}})}();
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*
|
||||
* Version: 5.10.2 (2021-11-17)
|
||||
*/
|
||||
!function(){"use strict";function i(t){function o(o,e){t=t.replace(o,e)}return t=n.trim(t),o(/\n/gi,"<br />"),o(/\[b\]/gi,"<strong>"),o(/\[\/b\]/gi,"</strong>"),o(/\[i\]/gi,"<em>"),o(/\[\/i\]/gi,"</em>"),o(/\[u\]/gi,"<u>"),o(/\[\/u\]/gi,"</u>"),o(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,'<a href="$1">$2</a>'),o(/\[url\](.*?)\[\/url\]/gi,'<a href="$1">$1</a>'),o(/\[img\](.*?)\[\/img\]/gi,'<img src="$1" />'),o(/\[color=(.*?)\](.*?)\[\/color\]/gi,'<font color="$1">$2</font>'),o(/\[code\](.*?)\[\/code\]/gi,'<span class="codeStyle">$1</span> '),o(/\[quote.*?\](.*?)\[\/quote\]/gi,'<span class="quoteStyle">$1</span> '),t}var o=tinymce.util.Tools.resolve("tinymce.PluginManager"),n=tinymce.util.Tools.resolve("tinymce.util.Tools");o.add("bbcode",function(o){o.on("BeforeSetContent",function(o){o.content=i(o.content)}),o.on("PostProcess",function(o){function e(o,e){t=t.replace(o,e)}var t;o.set&&(o.content=i(o.content)),o.get&&(o.content=(t=o.content,t=n.trim(t),e(/<a.*?href=\"(.*?)\".*?>(.*?)<\/a>/gi,"[url=$1]$2[/url]"),e(/<font.*?color=\"(.*?)\".*?class=\"codeStyle\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),e(/<font.*?color=\"(.*?)\".*?class=\"quoteStyle\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),e(/<font.*?class=\"codeStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),e(/<font.*?class=\"quoteStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),e(/<span style=\"color: ?(.*?);\">(.*?)<\/span>/gi,"[color=$1]$2[/color]"),e(/<font.*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[color=$1]$2[/color]"),e(/<span style=\"font-size:(.*?);\">(.*?)<\/span>/gi,"[size=$1]$2[/size]"),e(/<font>(.*?)<\/font>/gi,"$1"),e(/<img.*?src=\"(.*?)\".*?\/>/gi,"[img]$1[/img]"),e(/<span class=\"codeStyle\">(.*?)<\/span>/gi,"[code]$1[/code]"),e(/<span class=\"quoteStyle\">(.*?)<\/span>/gi,"[quote]$1[/quote]"),e(/<strong class=\"codeStyle\">(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]"),e(/<strong class=\"quoteStyle\">(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]"),e(/<em class=\"codeStyle\">(.*?)<\/em>/gi,"[code][i]$1[/i][/code]"),e(/<em class=\"quoteStyle\">(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]"),e(/<u class=\"codeStyle\">(.*?)<\/u>/gi,"[code][u]$1[/u][/code]"),e(/<u class=\"quoteStyle\">(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]"),e(/<\/(strong|b)>/gi,"[/b]"),e(/<(strong|b)>/gi,"[b]"),e(/<\/(em|i)>/gi,"[/i]"),e(/<(em|i)>/gi,"[i]"),e(/<\/u>/gi,"[/u]"),e(/<span style=\"text-decoration: ?underline;\">(.*?)<\/span>/gi,"[u]$1[/u]"),e(/<u>/gi,"[u]"),e(/<blockquote[^>]*>/gi,"[quote]"),e(/<\/blockquote>/gi,"[/quote]"),e(/<br \/>/gi,"\n"),e(/<br\/>/gi,"\n"),e(/<br>/gi,"\n"),e(/<p>/gi,""),e(/<\/p>/gi,"\n"),e(/ |\u00a0/gi," "),e(/"/gi,'"'),e(/</gi,"<"),e(/>/gi,">"),e(/&/gi,"&"),t))})})}();
|
||||
10
public/libs/tinymce/plugins/code/plugin.min.js
vendored
@@ -1 +1,9 @@
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),n=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),o=function(t){return t.getParam("code_dialog_width",600)},i=function(t){return t.getParam("code_dialog_height",Math.min(n.DOM.getViewPort().h-200,500))},c=function(t,n){t.focus(),t.undoManager.transact(function(){t.setContent(n)}),t.selection.setCursorLocation(),t.nodeChanged()},d=function(t){return t.getContent({source_view:!0})},e=function(n){var t=o(n),e=i(n);n.windowManager.open({title:"Source code",body:{type:"textbox",name:"code",multiline:!0,minWidth:t,minHeight:e,spellcheck:!1,style:"direction: ltr; text-align: left"},onSubmit:function(t){c(n,t.data.code)}}).find("#code").value(d(n))},u=function(t){t.addCommand("mceCodeEditor",function(){e(t)})},a=function(t){t.addButton("code",{icon:"code",tooltip:"Source code",onclick:function(){e(t)}}),t.addMenuItem("code",{icon:"code",text:"Source code",onclick:function(){e(t)}})};t.add("code",function(t){return u(t),a(t),{}})}();
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*
|
||||
* Version: 5.10.2 (2021-11-17)
|
||||
*/
|
||||
!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",function(e){var t,o;function n(){return o.execCommand("mceCodeEditor")}return(t=e).addCommand("mceCodeEditor",function(){var n,e;e=(n=t).getContent({source_view:!0}),n.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:e},onSubmit:function(e){var t=n,o=e.getData().code;t.focus(),t.undoManager.transact(function(){t.setContent(o)}),t.selection.setCursorLocation(),t.nodeChanged(),e.close()}})}),(o=e).ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:n}),o.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:n}),{}})}();
|
||||
138
public/libs/tinymce/plugins/codesample/css/prism.css
vendored
@@ -1,138 +0,0 @@
|
||||
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #a67f59;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
@@ -1 +1,9 @@
|
||||
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),l=tinymce.util.Tools.resolve("tinymce.util.Color"),a=function(e,n){e.find("#preview")[0].getEl().style.background=n},o=function(e,n){var i=l(n),t=i.toRgb();e.fromJSON({r:t.r,g:t.g,b:t.b,hex:i.toHex().substr(1)}),a(e,i.toHex())},t=function(e,n,i){var t=e.windowManager.open({title:"Color",items:{type:"container",layout:"flex",direction:"row",align:"stretch",padding:5,spacing:10,items:[{type:"colorpicker",value:i,onchange:function(){var e=this.rgb();t&&(t.find("#r").value(e.r),t.find("#g").value(e.g),t.find("#b").value(e.b),t.find("#hex").value(this.value().substr(1)),a(t,this.value()))}},{type:"form",padding:0,labelGap:5,defaults:{type:"textbox",size:7,value:"0",flex:1,spellcheck:!1,onchange:function(){var e,n,i=t.find("colorpicker")[0];if(e=this.name(),n=this.value(),"hex"===e)return o(t,n="#"+n),void i.value(n);n={r:t.find("#r").value(),g:t.find("#g").value(),b:t.find("#b").value()},i.value(n),o(t,n)}},items:[{name:"r",label:"R",autofocus:1},{name:"g",label:"G"},{name:"b",label:"B"},{name:"hex",label:"#",value:"000000"},{name:"preview",type:"container",border:1}]}]},onSubmit:function(){n("#"+t.toJSON().hex)}});o(t,i)};e.add("colorpicker",function(i){i.settings.color_picker_callback||(i.settings.color_picker_callback=function(e,n){t(i,e,n)})})}();
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*
|
||||
* Version: 5.10.2 (2021-11-17)
|
||||
*/
|
||||
!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("colorpicker",function(){})}();
|
||||
@@ -1 +1,9 @@
|
||||
!function(){"use strict";var o=function(t){var n=t,e=function(){return n};return{get:e,set:function(t){n=t},clone:function(){return o(e())}}},t=tinymce.util.Tools.resolve("tinymce.PluginManager"),i=function(t){return{isContextMenuVisible:function(){return t.get()}}},r=function(t){return t.settings.contextmenu_never_use_native},u=function(t){return t.getParam("contextmenu","link openlink image inserttable | cell row column deletetable")},l=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),s=function(t){return l.DOM.select(t.settings.ui_container)[0]},a=function(t,n){return{x:t,y:n}},f=function(t,n,e){return a(t.x+n,t.y+e)},m=function(t,n){if(t&&"static"!==l.DOM.getStyle(t,"position",!0)){var e=l.DOM.getPos(t),o=e.x-t.scrollLeft,i=e.y-t.scrollTop;return f(n,-o,-i)}return f(n,0,0)},c=function(t,n){if(t.inline)return m(s(t),a((u=n).pageX,u.pageY));var e,o,i,r,u,c=(e=t.getContentAreaContainer(),o=a((r=n).clientX,r.clientY),i=l.DOM.getPos(e),f(o,i.x,i.y));return m(s(t),c)},g=tinymce.util.Tools.resolve("tinymce.ui.Factory"),v=tinymce.util.Tools.resolve("tinymce.util.Tools"),y=function(t,n,e,o){null===o.get()?o.set(function(e,n){var t,o,i=[];o=u(e),v.each(o.split(/[ ,]/),function(t){var n=e.menuItems[t];"|"===t&&(n={text:t}),n&&(n.shortcut="",i.push(n))});for(var r=0;r<i.length;r++)"|"===i[r].text&&(0!==r&&r!==i.length-1||i.splice(r,1));return(t=g.create("menu",{items:i,context:"contextmenu",classes:"contextmenu"})).uiContainer=s(e),t.renderTo(s(e)),t.on("hide",function(t){t.control===this&&n.set(!1)}),e.on("remove",function(){t.remove(),t=null}),t}(t,e)):o.get().show(),o.get().moveTo(n.x,n.y),e.set(!0)},x=function(e,o,i){e.on("contextmenu",function(t){var n;n=e,(!t.ctrlKey||r(n))&&(t.preventDefault(),y(e,c(e,t),o,i))})};t.add("contextmenu",function(t){var n=o(null),e=o(!1);return x(t,e,n),i(e)})}();
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*
|
||||
* Version: 5.10.2 (2021-11-17)
|
||||
*/
|
||||
!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("contextmenu",function(){})}();
|
||||
|
Before Width: | Height: | Size: 354 B |
|
Before Width: | Height: | Size: 329 B |
|
Before Width: | Height: | Size: 331 B |
|
Before Width: | Height: | Size: 342 B |
|
Before Width: | Height: | Size: 340 B |
|
Before Width: | Height: | Size: 336 B |
|
Before Width: | Height: | Size: 338 B |
|
Before Width: | Height: | Size: 343 B |
|
Before Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 323 B |
|
Before Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 338 B |
|
Before Width: | Height: | Size: 328 B |
|
Before Width: | Height: | Size: 337 B |
|
Before Width: | Height: | Size: 350 B |
|
Before Width: | Height: | Size: 336 B |
@@ -1 +0,0 @@
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),e=tinymce.util.Tools.resolve("tinymce.util.Tools"),n=[["cool","cry","embarassed","foot-in-mouth"],["frown","innocent","kiss","laughing"],["money-mouth","sealed","smile","surprised"],["tongue-out","undecided","wink","yell"]],i=function(i){var o;return o='<table role="list" class="mce-grid">',e.each(n,function(t){o+="<tr>",e.each(t,function(t){var e=i+"/img/smiley-"+t+".gif";o+='<td><a href="#" data-mce-url="'+e+'" data-mce-alt="'+t+'" tabindex="-1" role="option" aria-label="'+t+'"><img src="'+e+'" style="width: 18px; height: 18px" role="presentation" /></a></td>'}),o+="</tr>"}),o+="</table>"},o=function(a,t){var e=i(t);a.addButton("emoticons",{type:"panelbutton",panel:{role:"application",autohide:!0,html:e,onclick:function(t){var e,i,o,n=a.dom.getParent(t.target,"a");n&&(e=a,i=n.getAttribute("data-mce-url"),o=n.getAttribute("data-mce-alt"),e.insertContent(e.dom.createHTML("img",{src:i,alt:o})),this.hide())}},tooltip:"Emoticons"})};t.add("emoticons",function(t,e){o(t,e)})}();
|
||||
|
Before Width: | Height: | Size: 13 KiB |
10
public/libs/tinymce/plugins/help/plugin.min.js
vendored
10
public/libs/tinymce/plugins/hr/plugin.min.js
vendored
@@ -1 +1,9 @@
|
||||
!function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=function(n){n.addCommand("InsertHorizontalRule",function(){n.execCommand("mceInsertContent",!1,"<hr />")})},o=function(n){n.addButton("hr",{icon:"hr",tooltip:"Horizontal line",cmd:"InsertHorizontalRule"}),n.addMenuItem("hr",{icon:"hr",text:"Horizontal line",cmd:"InsertHorizontalRule",context:"insert"})};n.add("hr",function(n){t(n),o(n)})}();
|
||||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*
|
||||
* Version: 5.10.2 (2021-11-17)
|
||||
*/
|
||||
!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("hr",function(n){var o,t;function e(){return t.execCommand("InsertHorizontalRule")}(o=n).addCommand("InsertHorizontalRule",function(){o.execCommand("mceInsertContent",!1,"<hr />")}),(t=n).ui.registry.addButton("hr",{icon:"horizontal-rule",tooltip:"Horizontal line",onAction:e}),t.ui.registry.addMenuItem("hr",{icon:"horizontal-rule",text:"Horizontal line",onAction:e})})}();
|
||||