mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-06 19:06:02 +03:00
Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf0ba9f756 | ||
|
|
05f8034439 | ||
|
|
1d1186c901 | ||
|
|
641a26cdf7 | ||
|
|
5fd8e7e0e9 | ||
|
|
d926ca5f71 | ||
|
|
b69722c3b5 | ||
|
|
c9aa1c979f | ||
|
|
49498cfaf9 | ||
|
|
3a4aa81115 | ||
|
|
f021823287 | ||
|
|
3a8a476906 | ||
|
|
328bc88f02 | ||
|
|
2a99e23e6d | ||
|
|
b855bbaaea | ||
|
|
96436839f1 | ||
|
|
b4f29a85ab | ||
|
|
4a2a044f3d | ||
|
|
ca09ed916f | ||
|
|
dbefda055f | ||
|
|
93ef8c97b6 | ||
|
|
420b29f32f | ||
|
|
d2ed98d20d | ||
|
|
ebc69a8f2c | ||
|
|
44013721f0 | ||
|
|
16222de5fa | ||
|
|
ebfe946160 | ||
|
|
5d2aad6a9e | ||
|
|
8fb016d1bf | ||
|
|
c216a6a210 | ||
|
|
26af9acc6c | ||
|
|
c8a7acb6c7 | ||
|
|
d3b39fbe50 | ||
|
|
ac7b2dd1bf | ||
|
|
f1a8ad4980 | ||
|
|
d5b7fff102 | ||
|
|
0930e8519c | ||
|
|
ff8dadefee | ||
|
|
2b0ae23da0 | ||
|
|
63cb6015a8 | ||
|
|
5a7fb20116 | ||
|
|
829f808800 | ||
|
|
0dfe5cb66b | ||
|
|
14bccae6bd | ||
|
|
b97c150ac8 | ||
|
|
0c5723d76e | ||
|
|
bec61a56c0 | ||
|
|
1b46aa8756 | ||
|
|
f14e6e8f2d | ||
|
|
0003ce61cd | ||
|
|
d76bbb2954 | ||
|
|
478067483f | ||
|
|
eff539f89b | ||
|
|
214992650d | ||
|
|
492ffff0a4 | ||
|
|
956eb1308f | ||
|
|
0cc215f8c3 | ||
|
|
e8e38f1f7b | ||
|
|
7dc80a9e14 | ||
|
|
e49afdbd72 | ||
|
|
56254bdb66 | ||
|
|
25654b2322 | ||
|
|
27339079f7 | ||
|
|
55e52e45fb | ||
|
|
c979e6465e | ||
|
|
c30a9d3564 | ||
|
|
59d1fb2d10 | ||
|
|
08a8c0070e | ||
|
|
cb770c534d | ||
|
|
6749faa89a | ||
|
|
82e8b1577e | ||
|
|
4dce03c0d3 | ||
|
|
affae2e3c4 | ||
|
|
1a90b98b8f | ||
|
|
da4308bb0f | ||
|
|
135022136a | ||
|
|
12f96bb1a4 | ||
|
|
678314a0c5 | ||
|
|
0887c39694 | ||
|
|
078e8e7dc3 | ||
|
|
038015f852 | ||
|
|
7c12920dc8 | ||
|
|
895f656897 | ||
|
|
31dbf132b9 | ||
|
|
b5281bc9ca | ||
|
|
3625f12abe | ||
|
|
55d61fceb2 | ||
|
|
2325a307a5 | ||
|
|
d2b49084b0 | ||
|
|
8594f42584 | ||
|
|
dd7463259a | ||
|
|
d23b24b8db | ||
|
|
1c859e94e0 | ||
|
|
981807220c | ||
|
|
a2231c3604 | ||
|
|
622adc5450 | ||
|
|
95e496d16f | ||
|
|
883e18f7c4 | ||
|
|
c5aad29c72 | ||
|
|
ea62fe6004 | ||
|
|
5ae9ed1e22 | ||
|
|
b6be8a2bb9 | ||
|
|
65dd7ad1e9 | ||
|
|
f991948c49 | ||
|
|
ee6a2339b6 | ||
|
|
fd26f54b99 | ||
|
|
6252b46395 | ||
|
|
20ecaa5c5a |
@@ -223,6 +223,7 @@ LDAP_DUMP_USER_DETAILS=false
|
||||
LDAP_USER_TO_GROUPS=false
|
||||
LDAP_GROUP_ATTRIBUTE="memberOf"
|
||||
LDAP_REMOVE_FROM_GROUPS=false
|
||||
LDAP_DUMP_USER_GROUPS=false
|
||||
|
||||
# SAML authentication configuration
|
||||
# Refer to https://www.bookstackapp.com/docs/admin/saml2-auth/
|
||||
@@ -273,7 +274,7 @@ AVATAR_URL=
|
||||
# Enable diagrams.net integration
|
||||
# Can simply be true/false to enable/disable the integration.
|
||||
# Alternatively, It can be URL to the diagrams.net instance you want to use.
|
||||
# For URLs, The following URL parameters should be included: embed=1&proto=json&spin=1
|
||||
# For URLs, The following URL parameters should be included: embed=1&proto=json&spin=1&configure=1
|
||||
DRAWIO=true
|
||||
|
||||
# Default item listing view
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/config.yml
vendored
10
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,9 +1,13 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discord chat support
|
||||
- name: Discord Chat Support
|
||||
url: https://discord.gg/ztkBqR2
|
||||
about: Realtime support / chat with the community and the team.
|
||||
about: Realtime support & chat with the BookStack community and the team.
|
||||
|
||||
- name: Debugging & Common Issues
|
||||
url: https://www.bookstackapp.com/docs/admin/debugging/
|
||||
about: Find details on how to debug issues and view common issues with thier resolutions.
|
||||
about: Find details on how to debug issues and view common issues with their resolutions.
|
||||
|
||||
- name: Official Support Plans
|
||||
url: https://www.bookstackapp.com/support/
|
||||
about: View our official support plans that offer assured support for business.
|
||||
13
.github/translators.txt
vendored
13
.github/translators.txt
vendored
@@ -165,7 +165,7 @@ Francesco Franchina (ffranchina) :: Italian
|
||||
Aimrane Kds (aimrane.kds) :: Arabic
|
||||
whenwesober :: Indonesian
|
||||
Rem (remkovdhoef) :: Dutch
|
||||
syn7ax69 :: Bulgarian; Turkish
|
||||
syn7ax69 :: Bulgarian; Turkish; German
|
||||
Blaade :: French
|
||||
Behzad HosseinPoor (behzad.hp) :: Persian
|
||||
Ole Aldric (Swoy) :: Norwegian Bokmal
|
||||
@@ -232,3 +232,14 @@ msevgen :: Turkish
|
||||
Khroners :: French
|
||||
MASOUD HOSSEINY (masoudme) :: Persian
|
||||
Thomerson Roncally (roncallyt) :: Portuguese, Brazilian
|
||||
metaarch :: Bulgarian
|
||||
Xabi (xabikip) :: Basque
|
||||
pedromcsousa :: Portuguese
|
||||
Nir Louk (looknear) :: Hebrew
|
||||
Alex (qianmengnet) :: Chinese Simplified
|
||||
stothew :: German
|
||||
sgenc :: Turkish
|
||||
Shukrullo (vodiylik) :: Uzbek
|
||||
William W. (Nevnt) :: Chinese Traditional
|
||||
eamaro :: Portuguese
|
||||
Ypsilon-dev :: Arabic
|
||||
|
||||
@@ -3,17 +3,14 @@
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Model;
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -24,31 +21,16 @@ class DispatchWebhookJob implements ShouldQueue
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* @var Webhook
|
||||
*/
|
||||
protected $webhook;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $event;
|
||||
protected Webhook $webhook;
|
||||
protected string $event;
|
||||
protected User $initiator;
|
||||
protected int $initiatedTime;
|
||||
|
||||
/**
|
||||
* @var string|Loggable
|
||||
*/
|
||||
protected $detail;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
protected $initiator;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $initiatedTime;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@@ -70,8 +52,8 @@ class DispatchWebhookJob implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $this->event, $this->webhook, $this->detail);
|
||||
$webhookData = $themeResponse ?? $this->buildWebhookData();
|
||||
$themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $this->event, $this->webhook, $this->detail, $this->initiator, $this->initiatedTime);
|
||||
$webhookData = $themeResponse ?? WebhookFormatter::getDefault($this->event, $this->webhook, $this->detail, $this->initiator, $this->initiatedTime)->format();
|
||||
$lastError = null;
|
||||
|
||||
try {
|
||||
@@ -97,36 +79,4 @@ class DispatchWebhookJob implements ShouldQueue
|
||||
|
||||
$this->webhook->save();
|
||||
}
|
||||
|
||||
protected function buildWebhookData(): array
|
||||
{
|
||||
$textParts = [
|
||||
$this->initiator->name,
|
||||
trans('activities.' . $this->event),
|
||||
];
|
||||
|
||||
if ($this->detail instanceof Entity) {
|
||||
$textParts[] = '"' . $this->detail->name . '"';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'event' => $this->event,
|
||||
'text' => implode(' ', $textParts),
|
||||
'triggered_at' => Carbon::createFromTimestampUTC($this->initiatedTime)->toISOString(),
|
||||
'triggered_by' => $this->initiator->attributesToArray(),
|
||||
'triggered_by_profile_url' => $this->initiator->getProfileUrl(),
|
||||
'webhook_id' => $this->webhook->id,
|
||||
'webhook_name' => $this->webhook->name,
|
||||
];
|
||||
|
||||
if (method_exists($this->detail, 'getUrl')) {
|
||||
$data['url'] = $this->detail->getUrl();
|
||||
}
|
||||
|
||||
if ($this->detail instanceof Model) {
|
||||
$data['related_item'] = $this->detail->attributesToArray();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ class TagRepo
|
||||
'name',
|
||||
($searchTerm || $nameFilter) ? 'value' : DB::raw('COUNT(distinct value) as `values`'),
|
||||
DB::raw('COUNT(id) as usages'),
|
||||
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Page\', 1, 0)) as page_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Chapter\', 1, 0)) as chapter_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Book\', 1, 0)) as book_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'BookStack\\\\BookShelf\', 1, 0)) as shelf_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'page\', 1, 0)) as page_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'chapter\', 1, 0)) as chapter_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'book\', 1, 0)) as book_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'),
|
||||
])
|
||||
->orderBy($nameFilter ? 'value' : 'name');
|
||||
|
||||
|
||||
124
app/Actions/WebhookFormatter.php
Normal file
124
app/Actions/WebhookFormatter.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Actions;
|
||||
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class WebhookFormatter
|
||||
{
|
||||
protected Webhook $webhook;
|
||||
protected string $event;
|
||||
protected User $initiator;
|
||||
protected int $initiatedTime;
|
||||
|
||||
/**
|
||||
* @var string|Loggable
|
||||
*/
|
||||
protected $detail;
|
||||
|
||||
/**
|
||||
* @var array{condition: callable(string, Model):bool, format: callable(Model):void}[]
|
||||
*/
|
||||
protected $modelFormatters = [];
|
||||
|
||||
public function __construct(string $event, Webhook $webhook, $detail, User $initiator, int $initiatedTime)
|
||||
{
|
||||
$this->webhook = $webhook;
|
||||
$this->event = $event;
|
||||
$this->initiator = $initiator;
|
||||
$this->initiatedTime = $initiatedTime;
|
||||
$this->detail = is_object($detail) ? clone $detail : $detail;
|
||||
}
|
||||
|
||||
public function format(): array
|
||||
{
|
||||
$data = [
|
||||
'event' => $this->event,
|
||||
'text' => $this->formatText(),
|
||||
'triggered_at' => Carbon::createFromTimestampUTC($this->initiatedTime)->toISOString(),
|
||||
'triggered_by' => $this->initiator->attributesToArray(),
|
||||
'triggered_by_profile_url' => $this->initiator->getProfileUrl(),
|
||||
'webhook_id' => $this->webhook->id,
|
||||
'webhook_name' => $this->webhook->name,
|
||||
];
|
||||
|
||||
if (method_exists($this->detail, 'getUrl')) {
|
||||
$data['url'] = $this->detail->getUrl();
|
||||
}
|
||||
|
||||
if ($this->detail instanceof Model) {
|
||||
$data['related_item'] = $this->formatModel();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(string, Model):bool $condition
|
||||
* @param callable(Model):void $format
|
||||
*/
|
||||
public function addModelFormatter(callable $condition, callable $format): void
|
||||
{
|
||||
$this->modelFormatters[] = [
|
||||
'condition' => $condition,
|
||||
'format' => $format,
|
||||
];
|
||||
}
|
||||
|
||||
public function addDefaultModelFormatters(): void
|
||||
{
|
||||
// Load entity owner, creator, updater details
|
||||
$this->addModelFormatter(
|
||||
fn ($event, $model) => ($model instanceof Entity),
|
||||
fn ($model) => $model->load(['ownedBy', 'createdBy', 'updatedBy'])
|
||||
);
|
||||
|
||||
// Load revision detail for page update and create events
|
||||
$this->addModelFormatter(
|
||||
fn ($event, $model) => ($model instanceof Page && ($event === ActivityType::PAGE_CREATE || $event === ActivityType::PAGE_UPDATE)),
|
||||
fn ($model) => $model->load('currentRevision')
|
||||
);
|
||||
}
|
||||
|
||||
protected function formatModel(): array
|
||||
{
|
||||
/** @var Model $model */
|
||||
$model = $this->detail;
|
||||
$model->unsetRelations();
|
||||
|
||||
foreach ($this->modelFormatters as $formatter) {
|
||||
if ($formatter['condition']($this->event, $model)) {
|
||||
$formatter['format']($model);
|
||||
}
|
||||
}
|
||||
|
||||
return $model->toArray();
|
||||
}
|
||||
|
||||
protected function formatText(): string
|
||||
{
|
||||
$textParts = [
|
||||
$this->initiator->name,
|
||||
trans('activities.' . $this->event),
|
||||
];
|
||||
|
||||
if ($this->detail instanceof Entity) {
|
||||
$textParts[] = '"' . $this->detail->name . '"';
|
||||
}
|
||||
|
||||
return implode(' ', $textParts);
|
||||
}
|
||||
|
||||
public static function getDefault(string $event, Webhook $webhook, $detail, User $initiator, int $initiatedTime): self
|
||||
{
|
||||
$instance = new self($event, $webhook, $detail, $initiator, $initiatedTime);
|
||||
$instance->addDefaultModelFormatters();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace BookStack\Auth\Access\Guards;
|
||||
use BookStack\Auth\Access\LdapService;
|
||||
use BookStack\Auth\Access\RegistrationService;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\JsonDebugException;
|
||||
use BookStack\Exceptions\LdapException;
|
||||
use BookStack\Exceptions\LoginAttemptEmailNeededException;
|
||||
use BookStack\Exceptions\LoginAttemptException;
|
||||
@@ -15,7 +16,7 @@ use Illuminate\Support\Str;
|
||||
|
||||
class LdapSessionGuard extends ExternalBaseSessionGuard
|
||||
{
|
||||
protected $ldapService;
|
||||
protected LdapService $ldapService;
|
||||
|
||||
/**
|
||||
* LdapSessionGuard constructor.
|
||||
@@ -59,8 +60,9 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
|
||||
* @param array $credentials
|
||||
* @param bool $remember
|
||||
*
|
||||
* @throws LdapException*@throws \BookStack\Exceptions\JsonDebugException
|
||||
* @throws LoginAttemptException
|
||||
* @throws LdapException
|
||||
* @throws JsonDebugException
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
@@ -15,12 +15,17 @@ use Illuminate\Support\Facades\Log;
|
||||
*/
|
||||
class LdapService
|
||||
{
|
||||
protected $ldap;
|
||||
protected $groupSyncService;
|
||||
protected Ldap $ldap;
|
||||
protected GroupSyncService $groupSyncService;
|
||||
protected UserAvatars $userAvatars;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $ldapConnection;
|
||||
protected $userAvatars;
|
||||
protected $config;
|
||||
protected $enabled;
|
||||
|
||||
protected array $config;
|
||||
protected bool $enabled;
|
||||
|
||||
/**
|
||||
* LdapService constructor.
|
||||
@@ -274,6 +279,7 @@ class LdapService
|
||||
* Get the groups a user is a part of on ldap.
|
||||
*
|
||||
* @throws LdapException
|
||||
* @throws JsonDebugException
|
||||
*/
|
||||
public function getUserGroups(string $userName): array
|
||||
{
|
||||
@@ -285,8 +291,17 @@ class LdapService
|
||||
}
|
||||
|
||||
$userGroups = $this->groupFilter($user);
|
||||
$allGroups = $this->getGroupsRecursive($userGroups, []);
|
||||
|
||||
return $this->getGroupsRecursive($userGroups, []);
|
||||
if ($this->config['dump_user_groups']) {
|
||||
throw new JsonDebugException([
|
||||
'details_from_ldap' => $user,
|
||||
'parsed_direct_user_groups' => $userGroups,
|
||||
'parsed_recursive_user_groups' => $allGroups,
|
||||
]);
|
||||
}
|
||||
|
||||
return $allGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,6 +384,7 @@ class LdapService
|
||||
* Sync the LDAP groups to the user roles for the current user.
|
||||
*
|
||||
* @throws LdapException
|
||||
* @throws JsonDebugException
|
||||
*/
|
||||
public function syncGroups(User $user, string $username)
|
||||
{
|
||||
|
||||
@@ -71,7 +71,7 @@ return [
|
||||
'locale' => env('APP_LANG', 'en'),
|
||||
|
||||
// Locales available
|
||||
'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'de_informal', 'es', 'es_AR', 'et', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ru', 'th', 'tr', 'uk', 'vi', 'zh_CN', 'zh_TW'],
|
||||
'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'de_informal', 'es', 'es_AR', 'et', 'eu', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ru', 'th', 'tr', 'uk', 'uz', 'vi', 'zh_CN', 'zh_TW'],
|
||||
|
||||
// Application Fallback Locale
|
||||
'fallback_locale' => 'en',
|
||||
|
||||
@@ -119,6 +119,7 @@ return [
|
||||
'ldap' => [
|
||||
'server' => env('LDAP_SERVER', false),
|
||||
'dump_user_details' => env('LDAP_DUMP_USER_DETAILS', false),
|
||||
'dump_user_groups' => env('LDAP_DUMP_USER_GROUPS', false),
|
||||
'dn' => env('LDAP_DN', false),
|
||||
'pass' => env('LDAP_PASS', false),
|
||||
'base_dn' => env('LDAP_BASE_DN', false),
|
||||
|
||||
@@ -72,7 +72,7 @@ return [
|
||||
// to the server if the browser has a HTTPS connection. This will keep
|
||||
// the cookie from being sent to you if it can not be done securely.
|
||||
'secure' => env('SESSION_SECURE_COOKIE', null)
|
||||
?? Str::startsWith(env('APP_URL'), 'https:'),
|
||||
?? Str::startsWith(env('APP_URL', ''), 'https:'),
|
||||
|
||||
// HTTP Access Only
|
||||
// Setting this value to true will prevent JavaScript from accessing the
|
||||
|
||||
@@ -10,10 +10,16 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $deleted_by
|
||||
* @property string $deletable_type
|
||||
* @property int $deletable_id
|
||||
* @property Deletable $deletable
|
||||
*/
|
||||
class Deletion extends Model implements Loggable
|
||||
{
|
||||
protected $hidden = [];
|
||||
|
||||
/**
|
||||
* Get the related deletable record.
|
||||
*/
|
||||
|
||||
@@ -10,19 +10,23 @@ use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
|
||||
/**
|
||||
* Class Page.
|
||||
*
|
||||
* @property int $chapter_id
|
||||
* @property string $html
|
||||
* @property string $markdown
|
||||
* @property string $text
|
||||
* @property bool $template
|
||||
* @property bool $draft
|
||||
* @property int $revision_count
|
||||
* @property Chapter $chapter
|
||||
* @property Collection $attachments
|
||||
* @property int $chapter_id
|
||||
* @property string $html
|
||||
* @property string $markdown
|
||||
* @property string $text
|
||||
* @property bool $template
|
||||
* @property bool $draft
|
||||
* @property int $revision_count
|
||||
* @property string $editor
|
||||
* @property Chapter $chapter
|
||||
* @property Collection $attachments
|
||||
* @property Collection $revisions
|
||||
* @property PageRevision $currentRevision
|
||||
*/
|
||||
class Page extends BookChild
|
||||
{
|
||||
@@ -82,6 +86,19 @@ class Page extends BookChild
|
||||
->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current revision for the page if existing.
|
||||
*
|
||||
* @return PageRevision|null
|
||||
*/
|
||||
public function currentRevision(): HasOne
|
||||
{
|
||||
return $this->hasOne(PageRevision::class)
|
||||
->where('type', '=', 'version')
|
||||
->orderBy('created_at', 'desc')
|
||||
->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all revision instances assigned to this page.
|
||||
* Includes all types of revisions.
|
||||
@@ -117,16 +134,6 @@ class Page extends BookChild
|
||||
return url('/' . implode('/', $parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current revision for the page if existing.
|
||||
*
|
||||
* @return PageRevision|null
|
||||
*/
|
||||
public function getCurrentRevision()
|
||||
{
|
||||
return $this->revisions()->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this page for JSON display.
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
/**
|
||||
* Class PageRevision.
|
||||
*
|
||||
* @property mixed $id
|
||||
* @property int $page_id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property string $book_slug
|
||||
* @property int $created_by
|
||||
@@ -20,13 +22,15 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
* @property string $summary
|
||||
* @property string $markdown
|
||||
* @property string $html
|
||||
* @property string $text
|
||||
* @property int $revision_number
|
||||
* @property Page $page
|
||||
* @property-read ?User $createdBy
|
||||
*/
|
||||
class PageRevision extends Model
|
||||
{
|
||||
protected $fillable = ['name', 'html', 'text', 'markdown', 'summary'];
|
||||
protected $fillable = ['name', 'text', 'summary'];
|
||||
protected $hidden = ['html', 'markdown', 'restricted', 'text'];
|
||||
|
||||
/**
|
||||
* Get the user that created the page revision.
|
||||
|
||||
@@ -11,8 +11,8 @@ use Illuminate\Http\UploadedFile;
|
||||
|
||||
class BaseRepo
|
||||
{
|
||||
protected $tagRepo;
|
||||
protected $imageRepo;
|
||||
protected TagRepo $tagRepo;
|
||||
protected ImageRepo $imageRepo;
|
||||
|
||||
public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo)
|
||||
{
|
||||
@@ -58,6 +58,7 @@ class BaseRepo
|
||||
|
||||
if (isset($input['tags'])) {
|
||||
$this->tagRepo->saveTagsToEntity($entity, $input['tags']);
|
||||
$entity->touch();
|
||||
}
|
||||
|
||||
$entity->rebuildPermissions();
|
||||
|
||||
36
app/Entities/Repos/DeletionRepo.php
Normal file
36
app/Entities/Repos/DeletionRepo.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Repos;
|
||||
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Entities\Models\Deletion;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Facades\Activity;
|
||||
|
||||
class DeletionRepo
|
||||
{
|
||||
private TrashCan $trashCan;
|
||||
|
||||
public function __construct(TrashCan $trashCan)
|
||||
{
|
||||
$this->trashCan = $trashCan;
|
||||
}
|
||||
|
||||
public function restore(int $id): int
|
||||
{
|
||||
/** @var Deletion $deletion */
|
||||
$deletion = Deletion::query()->findOrFail($id);
|
||||
Activity::add(ActivityType::RECYCLE_BIN_RESTORE, $deletion);
|
||||
|
||||
return $this->trashCan->restoreFromDeletion($deletion);
|
||||
}
|
||||
|
||||
public function destroy(int $id): int
|
||||
{
|
||||
/** @var Deletion $deletion */
|
||||
$deletion = Deletion::query()->findOrFail($id);
|
||||
Activity::add(ActivityType::RECYCLE_BIN_DESTROY, $deletion);
|
||||
|
||||
return $this->trashCan->destroyFromDeletion($deletion);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Models\PageRevision;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
use BookStack\Entities\Tools\PageContent;
|
||||
use BookStack\Entities\Tools\PageEditorData;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Exceptions\MoveOperationException;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
@@ -217,11 +218,25 @@ class PageRepo
|
||||
}
|
||||
|
||||
$pageContent = new PageContent($page);
|
||||
if (!empty($input['markdown'] ?? '')) {
|
||||
$currentEditor = $page->editor ?: PageEditorData::getSystemDefaultEditor();
|
||||
$newEditor = $currentEditor;
|
||||
|
||||
$haveInput = isset($input['markdown']) || isset($input['html']);
|
||||
$inputEmpty = empty($input['markdown']) && empty($input['html']);
|
||||
|
||||
if ($haveInput && $inputEmpty) {
|
||||
$pageContent->setNewHTML('');
|
||||
} elseif (!empty($input['markdown']) && is_string($input['markdown'])) {
|
||||
$newEditor = 'markdown';
|
||||
$pageContent->setNewMarkdown($input['markdown']);
|
||||
} elseif (isset($input['html'])) {
|
||||
$newEditor = 'wysiwyg';
|
||||
$pageContent->setNewHTML($input['html']);
|
||||
}
|
||||
|
||||
if ($newEditor !== $currentEditor && userCan('editor-change')) {
|
||||
$page->editor = $newEditor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,8 +244,12 @@ class PageRepo
|
||||
*/
|
||||
protected function savePageRevision(Page $page, string $summary = null): PageRevision
|
||||
{
|
||||
$revision = new PageRevision($page->getAttributes());
|
||||
$revision = new PageRevision();
|
||||
|
||||
$revision->name = $page->name;
|
||||
$revision->html = $page->html;
|
||||
$revision->markdown = $page->markdown;
|
||||
$revision->text = $page->text;
|
||||
$revision->page_id = $page->id;
|
||||
$revision->slug = $page->slug;
|
||||
$revision->book_slug = $page->book->slug;
|
||||
@@ -260,10 +279,15 @@ class PageRepo
|
||||
return $page;
|
||||
}
|
||||
|
||||
// Otherwise save the data to a revision
|
||||
// Otherwise, save the data to a revision
|
||||
$draft = $this->getPageRevisionToUpdate($page);
|
||||
$draft->fill($input);
|
||||
if (setting('app-editor') !== 'markdown') {
|
||||
|
||||
if (!empty($input['markdown'])) {
|
||||
$draft->markdown = $input['markdown'];
|
||||
$draft->html = '';
|
||||
} else {
|
||||
$draft->html = $input['html'];
|
||||
$draft->markdown = '';
|
||||
}
|
||||
|
||||
|
||||
@@ -215,14 +215,16 @@ class ExportFormatter
|
||||
*/
|
||||
protected function containHtml(string $htmlContent): string
|
||||
{
|
||||
$imageTagsOutput = [];
|
||||
preg_match_all("/\<img.*?src\=(\'|\")(.*?)(\'|\").*?\>/i", $htmlContent, $imageTagsOutput);
|
||||
// Replace embed tags with images
|
||||
$htmlContent = preg_replace("/<embed (.*?)>/i", '<img $1>', $htmlContent);
|
||||
|
||||
// Replace image src with base64 encoded image strings
|
||||
// Replace image & embed src attributes with base64 encoded data strings
|
||||
$imageTagsOutput = [];
|
||||
preg_match_all("/<img .*?src=['\"](.*?)['\"].*?>/i", $htmlContent, $imageTagsOutput);
|
||||
if (isset($imageTagsOutput[0]) && count($imageTagsOutput[0]) > 0) {
|
||||
foreach ($imageTagsOutput[0] as $index => $imgMatch) {
|
||||
$oldImgTagString = $imgMatch;
|
||||
$srcString = $imageTagsOutput[2][$index];
|
||||
$srcString = $imageTagsOutput[1][$index];
|
||||
$imageEncoded = $this->imageService->imageUriToBase64($srcString);
|
||||
if ($imageEncoded === null) {
|
||||
$imageEncoded = $srcString;
|
||||
@@ -232,14 +234,13 @@ class ExportFormatter
|
||||
}
|
||||
}
|
||||
|
||||
// Replace any relative links with full system URL
|
||||
$linksOutput = [];
|
||||
preg_match_all("/\<a.*href\=(\'|\")(.*?)(\'|\").*?\>/i", $htmlContent, $linksOutput);
|
||||
|
||||
// Replace image src with base64 encoded image strings
|
||||
preg_match_all("/<a .*href=['\"](.*?)['\"].*?>/i", $htmlContent, $linksOutput);
|
||||
if (isset($linksOutput[0]) && count($linksOutput[0]) > 0) {
|
||||
foreach ($linksOutput[0] as $index => $linkMatch) {
|
||||
$oldLinkString = $linkMatch;
|
||||
$srcString = $linksOutput[2][$index];
|
||||
$srcString = $linksOutput[1][$index];
|
||||
if (strpos(trim($srcString), 'http') !== 0) {
|
||||
$newSrcString = url($srcString);
|
||||
$newLinkString = str_replace($srcString, $newSrcString, $oldLinkString);
|
||||
@@ -248,7 +249,6 @@ class ExportFormatter
|
||||
}
|
||||
}
|
||||
|
||||
// Replace any relative links with system domain
|
||||
return $htmlContent;
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ class ExportFormatter
|
||||
$text .= $this->pageToMarkdown($page) . "\n\n";
|
||||
}
|
||||
|
||||
return $text;
|
||||
return trim($text);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,12 +338,12 @@ class ExportFormatter
|
||||
$text = '# ' . $book->name . "\n\n";
|
||||
foreach ($bookTree as $bookChild) {
|
||||
if ($bookChild instanceof Chapter) {
|
||||
$text .= $this->chapterToMarkdown($bookChild);
|
||||
$text .= $this->chapterToMarkdown($bookChild) . "\n\n";
|
||||
} else {
|
||||
$text .= $this->pageToMarkdown($bookChild);
|
||||
$text .= $this->pageToMarkdown($bookChild) . "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $text;
|
||||
return trim($text);
|
||||
}
|
||||
}
|
||||
|
||||
28
app/Entities/Tools/Markdown/CheckboxConverter.php
Normal file
28
app/Entities/Tools/Markdown/CheckboxConverter.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Tools\Markdown;
|
||||
|
||||
use League\HTMLToMarkdown\Converter\ConverterInterface;
|
||||
use League\HTMLToMarkdown\ElementInterface;
|
||||
|
||||
class CheckboxConverter implements ConverterInterface
|
||||
{
|
||||
public function convert(ElementInterface $element): string
|
||||
{
|
||||
if (strtolower($element->getAttribute('type')) === 'checkbox') {
|
||||
$isChecked = $element->getAttribute('checked') === 'checked';
|
||||
|
||||
return $isChecked ? ' [x] ' : ' [ ] ';
|
||||
}
|
||||
|
||||
return $element->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedTags(): array
|
||||
{
|
||||
return ['input'];
|
||||
}
|
||||
}
|
||||
20
app/Entities/Tools/Markdown/CustomDivConverter.php
Normal file
20
app/Entities/Tools/Markdown/CustomDivConverter.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Tools\Markdown;
|
||||
|
||||
use League\HTMLToMarkdown\Converter\DivConverter;
|
||||
use League\HTMLToMarkdown\ElementInterface;
|
||||
|
||||
class CustomDivConverter extends DivConverter
|
||||
{
|
||||
public function convert(ElementInterface $element): string
|
||||
{
|
||||
// Clean up draw.io diagrams
|
||||
$drawIoDiagram = $element->getAttribute('drawio-diagram');
|
||||
if ($drawIoDiagram) {
|
||||
return "<div drawio-diagram=\"{$drawIoDiagram}\">{$element->getValue()}</div>\n\n";
|
||||
}
|
||||
|
||||
return parent::convert($element);
|
||||
}
|
||||
}
|
||||
25
app/Entities/Tools/Markdown/CustomImageConverter.php
Normal file
25
app/Entities/Tools/Markdown/CustomImageConverter.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Tools\Markdown;
|
||||
|
||||
use League\HTMLToMarkdown\Converter\ImageConverter;
|
||||
use League\HTMLToMarkdown\ElementInterface;
|
||||
|
||||
class CustomImageConverter extends ImageConverter
|
||||
{
|
||||
public function convert(ElementInterface $element): string
|
||||
{
|
||||
$parent = $element->getParent();
|
||||
|
||||
// Remain as HTML if within diagram block.
|
||||
$withinDrawing = $parent && !empty($parent->getAttribute('drawio-diagram'));
|
||||
if ($withinDrawing) {
|
||||
$src = e($element->getAttribute('src'));
|
||||
$alt = e($element->getAttribute('alt'));
|
||||
|
||||
return "<img src=\"{$src}\" alt=\"{$alt}\"/>";
|
||||
}
|
||||
|
||||
return parent::convert($element);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ class CustomParagraphConverter extends ParagraphConverter
|
||||
{
|
||||
public function convert(ElementInterface $element): string
|
||||
{
|
||||
$class = $element->getAttribute('class');
|
||||
$class = e($element->getAttribute('class'));
|
||||
if (strpos($class, 'callout') !== false) {
|
||||
return "<{$element->getTagName()} class=\"{$class}\">{$element->getValue()}</{$element->getTagName()}>\n\n";
|
||||
}
|
||||
|
||||
@@ -5,12 +5,10 @@ namespace BookStack\Entities\Tools\Markdown;
|
||||
use League\HTMLToMarkdown\Converter\BlockquoteConverter;
|
||||
use League\HTMLToMarkdown\Converter\CodeConverter;
|
||||
use League\HTMLToMarkdown\Converter\CommentConverter;
|
||||
use League\HTMLToMarkdown\Converter\DivConverter;
|
||||
use League\HTMLToMarkdown\Converter\EmphasisConverter;
|
||||
use League\HTMLToMarkdown\Converter\HardBreakConverter;
|
||||
use League\HTMLToMarkdown\Converter\HeaderConverter;
|
||||
use League\HTMLToMarkdown\Converter\HorizontalRuleConverter;
|
||||
use League\HTMLToMarkdown\Converter\ImageConverter;
|
||||
use League\HTMLToMarkdown\Converter\LinkConverter;
|
||||
use League\HTMLToMarkdown\Converter\ListBlockConverter;
|
||||
use League\HTMLToMarkdown\Converter\ListItemConverter;
|
||||
@@ -21,7 +19,7 @@ use League\HTMLToMarkdown\HtmlConverter;
|
||||
|
||||
class HtmlToMarkdown
|
||||
{
|
||||
protected $html;
|
||||
protected string $html;
|
||||
|
||||
public function __construct(string $html)
|
||||
{
|
||||
@@ -75,18 +73,20 @@ class HtmlToMarkdown
|
||||
$environment->addConverter(new BlockquoteConverter());
|
||||
$environment->addConverter(new CodeConverter());
|
||||
$environment->addConverter(new CommentConverter());
|
||||
$environment->addConverter(new DivConverter());
|
||||
$environment->addConverter(new CustomDivConverter());
|
||||
$environment->addConverter(new EmphasisConverter());
|
||||
$environment->addConverter(new HardBreakConverter());
|
||||
$environment->addConverter(new HeaderConverter());
|
||||
$environment->addConverter(new HorizontalRuleConverter());
|
||||
$environment->addConverter(new ImageConverter());
|
||||
$environment->addConverter(new CustomImageConverter());
|
||||
$environment->addConverter(new LinkConverter());
|
||||
$environment->addConverter(new ListBlockConverter());
|
||||
$environment->addConverter(new ListItemConverter());
|
||||
$environment->addConverter(new CustomParagraphConverter());
|
||||
$environment->addConverter(new PreformattedConverter());
|
||||
$environment->addConverter(new TextConverter());
|
||||
$environment->addConverter(new CheckboxConverter());
|
||||
$environment->addConverter(new SpacedTagFallbackConverter());
|
||||
|
||||
return $environment;
|
||||
}
|
||||
|
||||
35
app/Entities/Tools/Markdown/MarkdownToHtml.php
Normal file
35
app/Entities/Tools/Markdown/MarkdownToHtml.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Tools\Markdown;
|
||||
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use League\CommonMark\Block\Element\ListItem;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use League\CommonMark\Environment;
|
||||
use League\CommonMark\Extension\Table\TableExtension;
|
||||
use League\CommonMark\Extension\TaskList\TaskListExtension;
|
||||
|
||||
class MarkdownToHtml
|
||||
{
|
||||
protected string $markdown;
|
||||
|
||||
public function __construct(string $markdown)
|
||||
{
|
||||
$this->markdown = $markdown;
|
||||
}
|
||||
|
||||
public function convert(): string
|
||||
{
|
||||
$environment = Environment::createCommonMarkEnvironment();
|
||||
$environment->addExtension(new TableExtension());
|
||||
$environment->addExtension(new TaskListExtension());
|
||||
$environment->addExtension(new CustomStrikeThroughExtension());
|
||||
$environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment;
|
||||
$converter = new CommonMarkConverter([], $environment);
|
||||
|
||||
$environment->addBlockRenderer(ListItem::class, new CustomListItemRenderer(), 10);
|
||||
|
||||
return $converter->convertToHtml($this->markdown);
|
||||
}
|
||||
}
|
||||
23
app/Entities/Tools/Markdown/SpacedTagFallbackConverter.php
Normal file
23
app/Entities/Tools/Markdown/SpacedTagFallbackConverter.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Tools\Markdown;
|
||||
|
||||
use League\HTMLToMarkdown\Converter\ConverterInterface;
|
||||
use League\HTMLToMarkdown\ElementInterface;
|
||||
|
||||
/**
|
||||
* For certain defined tags, add additional spacing upon the retained HTML content
|
||||
* to separate it out from anything that may be markdown soon afterwards or within.
|
||||
*/
|
||||
class SpacedTagFallbackConverter implements ConverterInterface
|
||||
{
|
||||
public function convert(ElementInterface $element): string
|
||||
{
|
||||
return \html_entity_decode($element->getChildrenAsString()) . "\n\n";
|
||||
}
|
||||
|
||||
public function getSupportedTags(): array
|
||||
{
|
||||
return ['summary', 'iframe'];
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,8 @@
|
||||
namespace BookStack\Entities\Tools;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Tools\Markdown\CustomListItemRenderer;
|
||||
use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension;
|
||||
use BookStack\Entities\Tools\Markdown\MarkdownToHtml;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use BookStack\Uploads\ImageService;
|
||||
use BookStack\Util\HtmlContentFilter;
|
||||
@@ -17,15 +14,10 @@ use DOMNode;
|
||||
use DOMNodeList;
|
||||
use DOMXPath;
|
||||
use Illuminate\Support\Str;
|
||||
use League\CommonMark\Block\Element\ListItem;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use League\CommonMark\Environment;
|
||||
use League\CommonMark\Extension\Table\TableExtension;
|
||||
use League\CommonMark\Extension\TaskList\TaskListExtension;
|
||||
|
||||
class PageContent
|
||||
{
|
||||
protected $page;
|
||||
protected Page $page;
|
||||
|
||||
/**
|
||||
* PageContent constructor.
|
||||
@@ -53,28 +45,11 @@ class PageContent
|
||||
{
|
||||
$markdown = $this->extractBase64ImagesFromMarkdown($markdown);
|
||||
$this->page->markdown = $markdown;
|
||||
$html = $this->markdownToHtml($markdown);
|
||||
$html = (new MarkdownToHtml($markdown))->convert();
|
||||
$this->page->html = $this->formatHtml($html);
|
||||
$this->page->text = $this->toPlainText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given Markdown content to a HTML string.
|
||||
*/
|
||||
protected function markdownToHtml(string $markdown): string
|
||||
{
|
||||
$environment = Environment::createCommonMarkEnvironment();
|
||||
$environment->addExtension(new TableExtension());
|
||||
$environment->addExtension(new TaskListExtension());
|
||||
$environment->addExtension(new CustomStrikeThroughExtension());
|
||||
$environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment;
|
||||
$converter = new CommonMarkConverter([], $environment);
|
||||
|
||||
$environment->addBlockRenderer(ListItem::class, new CustomListItemRenderer(), 10);
|
||||
|
||||
return $converter->convertToHtml($markdown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert all base64 image data to saved images.
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class PageEditActivity
|
||||
{
|
||||
protected $page;
|
||||
protected Page $page;
|
||||
|
||||
/**
|
||||
* PageEditActivity constructor.
|
||||
|
||||
115
app/Entities/Tools/PageEditorData.php
Normal file
115
app/Entities/Tools/PageEditorData.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Tools;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Repos\PageRepo;
|
||||
use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
|
||||
use BookStack\Entities\Tools\Markdown\MarkdownToHtml;
|
||||
|
||||
class PageEditorData
|
||||
{
|
||||
protected Page $page;
|
||||
protected PageRepo $pageRepo;
|
||||
protected string $requestedEditor;
|
||||
|
||||
protected array $viewData;
|
||||
protected array $warnings;
|
||||
|
||||
public function __construct(Page $page, PageRepo $pageRepo, string $requestedEditor)
|
||||
{
|
||||
$this->page = $page;
|
||||
$this->pageRepo = $pageRepo;
|
||||
$this->requestedEditor = $requestedEditor;
|
||||
|
||||
$this->viewData = $this->build();
|
||||
}
|
||||
|
||||
public function getViewData(): array
|
||||
{
|
||||
return $this->viewData;
|
||||
}
|
||||
|
||||
public function getWarnings(): array
|
||||
{
|
||||
return $this->warnings;
|
||||
}
|
||||
|
||||
protected function build(): array
|
||||
{
|
||||
$page = clone $this->page;
|
||||
$isDraft = boolval($this->page->draft);
|
||||
$templates = $this->pageRepo->getTemplates(10);
|
||||
$draftsEnabled = auth()->check();
|
||||
|
||||
$isDraftRevision = false;
|
||||
$this->warnings = [];
|
||||
$editActivity = new PageEditActivity($page);
|
||||
|
||||
if ($editActivity->hasActiveEditing()) {
|
||||
$this->warnings[] = $editActivity->activeEditingMessage();
|
||||
}
|
||||
|
||||
// Check for a current draft version for this user
|
||||
$userDraft = $this->pageRepo->getUserDraft($page);
|
||||
if ($userDraft !== null) {
|
||||
$page->forceFill($userDraft->only(['name', 'html', 'markdown']));
|
||||
$isDraftRevision = true;
|
||||
$this->warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
|
||||
}
|
||||
|
||||
$editorType = $this->getEditorType($page);
|
||||
$this->updateContentForEditor($page, $editorType);
|
||||
|
||||
return [
|
||||
'page' => $page,
|
||||
'book' => $page->book,
|
||||
'isDraft' => $isDraft,
|
||||
'isDraftRevision' => $isDraftRevision,
|
||||
'draftsEnabled' => $draftsEnabled,
|
||||
'templates' => $templates,
|
||||
'editor' => $editorType,
|
||||
];
|
||||
}
|
||||
|
||||
protected function updateContentForEditor(Page $page, string $editorType): void
|
||||
{
|
||||
$isHtml = !empty($page->html) && empty($page->markdown);
|
||||
|
||||
// HTML to markdown-clean conversion
|
||||
if ($editorType === 'markdown' && $isHtml && $this->requestedEditor === 'markdown-clean') {
|
||||
$page->markdown = (new HtmlToMarkdown($page->html))->convert();
|
||||
}
|
||||
|
||||
// Markdown to HTML conversion if we don't have HTML
|
||||
if ($editorType === 'wysiwyg' && !$isHtml) {
|
||||
$page->html = (new MarkdownToHtml($page->markdown))->convert();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of editor to show for editing the given page.
|
||||
* Defaults based upon the current content of the page otherwise will fall back
|
||||
* to system default but will take a requested type (if provided) if permissions allow.
|
||||
*/
|
||||
protected function getEditorType(Page $page): string
|
||||
{
|
||||
$editorType = $page->editor ?: self::getSystemDefaultEditor();
|
||||
|
||||
// Use requested editor if valid and if we have permission
|
||||
$requestedType = explode('-', $this->requestedEditor)[0];
|
||||
if (($requestedType === 'markdown' || $requestedType === 'wysiwyg') && userCan('editor-change')) {
|
||||
$editorType = $requestedType;
|
||||
}
|
||||
|
||||
return $editorType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured system default editor.
|
||||
*/
|
||||
public static function getSystemDefaultEditor(): string
|
||||
{
|
||||
return setting('app-editor') === 'markdown' ? 'markdown' : 'wysiwyg';
|
||||
}
|
||||
}
|
||||
@@ -360,7 +360,7 @@ class SearchRunner
|
||||
/** @var Connection $connection */
|
||||
$connection = $query->getConnection();
|
||||
$tagValue = (float) trim($connection->getPdo()->quote($tagValue), "'");
|
||||
$query->whereRaw("value ${tagOperator} ${tagValue}");
|
||||
$query->whereRaw("value {$tagOperator} {$tagValue}");
|
||||
} else {
|
||||
$query->where('value', $tagOperator, $tagValue);
|
||||
}
|
||||
|
||||
@@ -19,10 +19,13 @@ class JsonDebugException extends Exception
|
||||
}
|
||||
|
||||
/**
|
||||
* Covert this exception into a response.
|
||||
* Convert this exception into a response.
|
||||
* We add a manual data conversion to UTF8 to ensure any binary data is presentable as a JSON string.
|
||||
*/
|
||||
public function render(): JsonResponse
|
||||
{
|
||||
return response()->json($this->data);
|
||||
$cleaned = mb_convert_encoding($this->data, 'UTF-8');
|
||||
|
||||
return response()->json($cleaned);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,14 +87,33 @@ class AttachmentApiController extends ApiController
|
||||
'markdown' => $attachment->markdownLink(),
|
||||
]);
|
||||
|
||||
if (!$attachment->external) {
|
||||
$attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
|
||||
$attachment->setAttribute('content', base64_encode($attachmentContents));
|
||||
} else {
|
||||
// Simply return a JSON response of the attachment for link-based attachments
|
||||
if ($attachment->external) {
|
||||
$attachment->setAttribute('content', $attachment->path);
|
||||
|
||||
return response()->json($attachment);
|
||||
}
|
||||
|
||||
return response()->json($attachment);
|
||||
// Build and split our core JSON, at point of content.
|
||||
$splitter = 'CONTENT_SPLIT_LOCATION_' . time() . '_' . rand(1, 40000);
|
||||
$attachment->setAttribute('content', $splitter);
|
||||
$json = $attachment->toJson();
|
||||
$jsonParts = explode($splitter, $json);
|
||||
// Get a stream for the file data from storage
|
||||
$stream = $this->attachmentService->streamAttachmentFromStorage($attachment);
|
||||
|
||||
return response()->stream(function () use ($jsonParts, $stream) {
|
||||
// Output the pre-content JSON data
|
||||
echo $jsonParts[0];
|
||||
|
||||
// Stream out our attachment data as base64 content
|
||||
stream_filter_append($stream, 'convert.base64-encode', STREAM_FILTER_READ);
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
|
||||
// Output our post-content JSON data
|
||||
echo $jsonParts[1];
|
||||
}, 200, ['Content-Type' => 'application/json']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,21 +11,20 @@ use Illuminate\Validation\ValidationException;
|
||||
|
||||
class BookshelfApiController extends ApiController
|
||||
{
|
||||
/**
|
||||
* @var BookshelfRepo
|
||||
*/
|
||||
protected $bookshelfRepo;
|
||||
protected BookshelfRepo $bookshelfRepo;
|
||||
|
||||
protected $rules = [
|
||||
'create' => [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'books' => ['array'],
|
||||
'tags' => ['array'],
|
||||
],
|
||||
'update' => [
|
||||
'name' => ['string', 'min:1', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'books' => ['array'],
|
||||
'tags' => ['array'],
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use Illuminate\Http\Request;
|
||||
|
||||
class PageApiController extends ApiController
|
||||
{
|
||||
protected $pageRepo;
|
||||
protected PageRepo $pageRepo;
|
||||
|
||||
protected $rules = [
|
||||
'create' => [
|
||||
@@ -24,8 +24,8 @@ class PageApiController extends ApiController
|
||||
'tags' => ['array'],
|
||||
],
|
||||
'update' => [
|
||||
'book_id' => ['required', 'integer'],
|
||||
'chapter_id' => ['required', 'integer'],
|
||||
'book_id' => ['integer'],
|
||||
'chapter_id' => ['integer'],
|
||||
'name' => ['string', 'min:1', 'max:255'],
|
||||
'html' => ['string'],
|
||||
'markdown' => ['string'],
|
||||
@@ -103,6 +103,8 @@ class PageApiController extends ApiController
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
$requestData = $this->validate($request, $this->rules['update']);
|
||||
|
||||
$page = $this->pageRepo->getById($id, []);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
@@ -127,7 +129,7 @@ class PageApiController extends ApiController
|
||||
}
|
||||
}
|
||||
|
||||
$updatedPage = $this->pageRepo->update($page, $request->all());
|
||||
$updatedPage = $this->pageRepo->update($page, $requestData);
|
||||
|
||||
return response()->json($updatedPage->forJsonDisplay());
|
||||
}
|
||||
|
||||
90
app/Http/Controllers/Api/RecycleBinApiController.php
Normal file
90
app/Http/Controllers/Api/RecycleBinApiController.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Http\Controllers\Api;
|
||||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Deletion;
|
||||
use BookStack\Entities\Repos\DeletionRepo;
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class RecycleBinApiController extends ApiController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(function ($request, $next) {
|
||||
$this->checkPermission('settings-manage');
|
||||
$this->checkPermission('restrictions-manage-all');
|
||||
|
||||
return $next($request);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a top-level listing of the items in the recycle bin.
|
||||
* The "deletable" property will reflect the main item deleted.
|
||||
* For books and chapters, counts of child pages/chapters will
|
||||
* be loaded within this "deletable" data.
|
||||
* For chapters & pages, the parent item will be loaded within this "deletable" data.
|
||||
* Requires permission to manage both system settings and permissions.
|
||||
*/
|
||||
public function list()
|
||||
{
|
||||
return $this->apiListingResponse(Deletion::query()->with('deletable'), [
|
||||
'id',
|
||||
'deleted_by',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deletable_type',
|
||||
'deletable_id',
|
||||
], [Closure::fromCallable([$this, 'listFormatter'])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a single deletion from the recycle bin.
|
||||
* Requires permission to manage both system settings and permissions.
|
||||
*/
|
||||
public function restore(DeletionRepo $deletionRepo, string $deletionId)
|
||||
{
|
||||
$restoreCount = $deletionRepo->restore(intval($deletionId));
|
||||
|
||||
return response()->json(['restore_count' => $restoreCount]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a single deletion from the recycle bin.
|
||||
* Use this endpoint carefully as it will entirely remove the underlying deleted items from the system.
|
||||
* Requires permission to manage both system settings and permissions.
|
||||
*/
|
||||
public function destroy(DeletionRepo $deletionRepo, string $deletionId)
|
||||
{
|
||||
$deleteCount = $deletionRepo->destroy(intval($deletionId));
|
||||
|
||||
return response()->json(['delete_count' => $deleteCount]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load some related details for the deletion listing.
|
||||
*/
|
||||
protected function listFormatter(Deletion $deletion)
|
||||
{
|
||||
$deletable = $deletion->deletable;
|
||||
$withTrashedQuery = fn (Builder $query) => $query->withTrashed();
|
||||
|
||||
if ($deletable instanceof BookChild) {
|
||||
$parent = $deletable->getParent();
|
||||
$parent->setAttribute('type', $parent->getType());
|
||||
$deletable->setRelation('parent', $parent);
|
||||
}
|
||||
|
||||
if ($deletable instanceof Book || $deletable instanceof Chapter) {
|
||||
$countsToLoad = ['pages' => $withTrashedQuery];
|
||||
if ($deletable instanceof Book) {
|
||||
$countsToLoad['chapters'] = $withTrashedQuery;
|
||||
}
|
||||
$deletable->loadCount($countsToLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AttachmentController extends Controller
|
||||
{
|
||||
protected $attachmentService;
|
||||
protected $pageRepo;
|
||||
protected AttachmentService $attachmentService;
|
||||
protected PageRepo $pageRepo;
|
||||
|
||||
/**
|
||||
* AttachmentController constructor.
|
||||
@@ -230,13 +230,13 @@ class AttachmentController extends Controller
|
||||
}
|
||||
|
||||
$fileName = $attachment->getFileName();
|
||||
$attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
|
||||
$attachmentStream = $this->attachmentService->streamAttachmentFromStorage($attachment);
|
||||
|
||||
if ($request->get('open') === 'true') {
|
||||
return $this->inlineDownloadResponse($attachmentContents, $fileName);
|
||||
return $this->streamedInlineDownloadResponse($attachmentStream, $fileName);
|
||||
}
|
||||
|
||||
return $this->downloadResponse($attachmentContents, $fileName);
|
||||
return $this->streamedDownloadResponse($attachmentStream, $fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
@@ -115,7 +116,30 @@ abstract class Controller extends BaseController
|
||||
{
|
||||
return response()->make($content, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
|
||||
'Content-Disposition' => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a response that forces a download, from a given stream of content.
|
||||
*/
|
||||
protected function streamedDownloadResponse($stream, string $fileName): StreamedResponse
|
||||
{
|
||||
return response()->stream(function () use ($stream) {
|
||||
|
||||
// End & flush the output buffer, if we're in one, otherwise we still use memory.
|
||||
// Output buffer may or may not exist depending on PHP `output_buffering` setting.
|
||||
// Ignore in testing since output buffers are used to gather a response.
|
||||
if (!empty(ob_get_status()) && !app()->runningUnitTests()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
}, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]);
|
||||
}
|
||||
@@ -130,7 +154,28 @@ abstract class Controller extends BaseController
|
||||
|
||||
return response()->make($content, 200, [
|
||||
'Content-Type' => $mime,
|
||||
'Content-Disposition' => 'inline; filename="' . $fileName . '"',
|
||||
'Content-Disposition' => 'inline; filename="' . str_replace('"', '', $fileName) . '"',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file download response that provides the file with a content-type
|
||||
* correct for the file, in a way so the browser can show the content in browser,
|
||||
* for a given content stream.
|
||||
*/
|
||||
protected function streamedInlineDownloadResponse($stream, string $fileName): StreamedResponse
|
||||
{
|
||||
$sniffContent = fread($stream, 1000);
|
||||
$mime = (new WebSafeMimeSniffer())->sniff($sniffContent);
|
||||
|
||||
return response()->stream(function () use ($sniffContent, $stream) {
|
||||
echo $sniffContent;
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
}, 200, [
|
||||
'Content-Type' => $mime,
|
||||
'Content-Disposition' => 'inline; filename="' . str_replace('"', '', $fileName) . '"',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]);
|
||||
}
|
||||
@@ -174,6 +219,6 @@ abstract class Controller extends BaseController
|
||||
*/
|
||||
protected function getImageValidationRules(): array
|
||||
{
|
||||
return ['image_extension', 'mimes:jpeg,png,gif,webp', 'max:' . (config('app.upload_limit') * 1000)];
|
||||
return ['image_extension', 'mimes:jpeg,png,gif,webp,svg', 'max:' . (config('app.upload_limit') * 1000)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,11 @@ class DrawioImageController extends Controller
|
||||
return $this->jsonError('Image data could not be found');
|
||||
}
|
||||
|
||||
$isSvg = strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) === 'svg';
|
||||
$uriPrefix = $isSvg ? 'data:image/svg+xml;base64,' : 'data:image/png;base64,';
|
||||
|
||||
return response()->json([
|
||||
'content' => base64_encode($imageData),
|
||||
'content' => $uriPrefix . base64_encode($imageData),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use BookStack\Entities\Tools\Cloner;
|
||||
use BookStack\Entities\Tools\NextPreviousContentLocator;
|
||||
use BookStack\Entities\Tools\PageContent;
|
||||
use BookStack\Entities\Tools\PageEditActivity;
|
||||
use BookStack\Entities\Tools\PageEditorData;
|
||||
use BookStack\Entities\Tools\PermissionsUpdater;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Exceptions\PermissionsException;
|
||||
@@ -21,7 +22,7 @@ use Throwable;
|
||||
|
||||
class PageController extends Controller
|
||||
{
|
||||
protected $pageRepo;
|
||||
protected PageRepo $pageRepo;
|
||||
|
||||
/**
|
||||
* PageController constructor.
|
||||
@@ -82,22 +83,15 @@ class PageController extends Controller
|
||||
*
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function editDraft(string $bookSlug, int $pageId)
|
||||
public function editDraft(Request $request, string $bookSlug, int $pageId)
|
||||
{
|
||||
$draft = $this->pageRepo->getById($pageId);
|
||||
$this->checkOwnablePermission('page-create', $draft->getParent());
|
||||
|
||||
$editorData = new PageEditorData($draft, $this->pageRepo, $request->query('editor', ''));
|
||||
$this->setPageTitle(trans('entities.pages_edit_draft'));
|
||||
|
||||
$draftsEnabled = $this->isSignedIn();
|
||||
$templates = $this->pageRepo->getTemplates(10);
|
||||
|
||||
return view('pages.edit', [
|
||||
'page' => $draft,
|
||||
'book' => $draft->book,
|
||||
'isDraft' => true,
|
||||
'draftsEnabled' => $draftsEnabled,
|
||||
'templates' => $templates,
|
||||
]);
|
||||
return view('pages.edit', $editorData->getViewData());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,43 +182,19 @@ class PageController extends Controller
|
||||
*
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function edit(string $bookSlug, string $pageSlug)
|
||||
public function edit(Request $request, string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
$page->isDraft = false;
|
||||
$editActivity = new PageEditActivity($page);
|
||||
|
||||
// Check for active editing
|
||||
$warnings = [];
|
||||
if ($editActivity->hasActiveEditing()) {
|
||||
$warnings[] = $editActivity->activeEditingMessage();
|
||||
$editorData = new PageEditorData($page, $this->pageRepo, $request->query('editor', ''));
|
||||
if ($editorData->getWarnings()) {
|
||||
$this->showWarningNotification(implode("\n", $editorData->getWarnings()));
|
||||
}
|
||||
|
||||
// Check for a current draft version for this user
|
||||
$userDraft = $this->pageRepo->getUserDraft($page);
|
||||
if ($userDraft !== null) {
|
||||
$page->forceFill($userDraft->only(['name', 'html', 'markdown']));
|
||||
$page->isDraft = true;
|
||||
$warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
|
||||
}
|
||||
|
||||
if (count($warnings) > 0) {
|
||||
$this->showWarningNotification(implode("\n", $warnings));
|
||||
}
|
||||
|
||||
$templates = $this->pageRepo->getTemplates(10);
|
||||
$draftsEnabled = $this->isSignedIn();
|
||||
$this->setPageTitle(trans('entities.pages_editing_named', ['pageName' => $page->getShortName()]));
|
||||
|
||||
return view('pages.edit', [
|
||||
'page' => $page,
|
||||
'book' => $page->book,
|
||||
'current' => $page,
|
||||
'draftsEnabled' => $draftsEnabled,
|
||||
'templates' => $templates,
|
||||
]);
|
||||
return view('pages.edit', $editorData->getViewData());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -124,11 +124,8 @@ class PageRevisionController extends Controller
|
||||
throw new NotFoundException("Revision #{$revId} not found");
|
||||
}
|
||||
|
||||
// Get the current revision for the page
|
||||
$currentRevision = $page->getCurrentRevision();
|
||||
|
||||
// Check if its the latest revision, cannot delete latest revision.
|
||||
if (intval($currentRevision->id) === intval($revId)) {
|
||||
// Check if it's the latest revision, cannot delete the latest revision.
|
||||
if (intval($page->currentRevision->id ?? null) === intval($revId)) {
|
||||
$this->showErrorNotification(trans('entities.revision_cannot_delete_latest'));
|
||||
|
||||
return redirect($page->getUrl('/revisions'));
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers;
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Entities\Models\Deletion;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Repos\DeletionRepo;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
|
||||
class RecycleBinController extends Controller
|
||||
@@ -73,12 +74,9 @@ class RecycleBinController extends Controller
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function restore(string $id)
|
||||
public function restore(DeletionRepo $deletionRepo, string $id)
|
||||
{
|
||||
/** @var Deletion $deletion */
|
||||
$deletion = Deletion::query()->findOrFail($id);
|
||||
$this->logActivity(ActivityType::RECYCLE_BIN_RESTORE, $deletion);
|
||||
$restoreCount = (new TrashCan())->restoreFromDeletion($deletion);
|
||||
$restoreCount = $deletionRepo->restore((int) $id);
|
||||
|
||||
$this->showSuccessNotification(trans('settings.recycle_bin_restore_notification', ['count' => $restoreCount]));
|
||||
|
||||
@@ -103,12 +101,9 @@ class RecycleBinController extends Controller
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function destroy(string $id)
|
||||
public function destroy(DeletionRepo $deletionRepo, string $id)
|
||||
{
|
||||
/** @var Deletion $deletion */
|
||||
$deletion = Deletion::query()->findOrFail($id);
|
||||
$this->logActivity(ActivityType::RECYCLE_BIN_DESTROY, $deletion);
|
||||
$deleteCount = (new TrashCan())->destroyFromDeletion($deletion);
|
||||
$deleteCount = $deletionRepo->destroy((int) $id);
|
||||
|
||||
$this->showSuccessNotification(trans('settings.recycle_bin_destroy_notification', ['count' => $deleteCount]));
|
||||
|
||||
|
||||
@@ -9,28 +9,37 @@ use Illuminate\Http\Request;
|
||||
|
||||
class SettingController extends Controller
|
||||
{
|
||||
protected $imageRepo;
|
||||
protected ImageRepo $imageRepo;
|
||||
|
||||
protected array $settingCategories = ['features', 'customization', 'registration'];
|
||||
|
||||
/**
|
||||
* SettingController constructor.
|
||||
*/
|
||||
public function __construct(ImageRepo $imageRepo)
|
||||
{
|
||||
$this->imageRepo = $imageRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the settings.
|
||||
* Handle requests to the settings index path.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return redirect('/settings/features');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the settings for the given category.
|
||||
*/
|
||||
public function category(string $category)
|
||||
{
|
||||
$this->ensureCategoryExists($category);
|
||||
$this->checkPermission('settings-manage');
|
||||
$this->setPageTitle(trans('settings.settings'));
|
||||
|
||||
// Get application version
|
||||
$version = trim(file_get_contents(base_path('version')));
|
||||
|
||||
return view('settings.index', [
|
||||
return view('settings.' . $category, [
|
||||
'category' => $category,
|
||||
'version' => $version,
|
||||
'guestUser' => User::getDefault(),
|
||||
]);
|
||||
@@ -39,8 +48,9 @@ class SettingController extends Controller
|
||||
/**
|
||||
* Update the specified settings in storage.
|
||||
*/
|
||||
public function update(Request $request)
|
||||
public function update(Request $request, string $category)
|
||||
{
|
||||
$this->ensureCategoryExists($category);
|
||||
$this->preventAccessInDemoMode();
|
||||
$this->checkPermission('settings-manage');
|
||||
$this->validate($request, [
|
||||
@@ -57,7 +67,7 @@ class SettingController extends Controller
|
||||
}
|
||||
|
||||
// Update logo image if set
|
||||
if ($request->hasFile('app_logo')) {
|
||||
if ($category === 'customization' && $request->hasFile('app_logo')) {
|
||||
$logoFile = $request->file('app_logo');
|
||||
$this->imageRepo->destroyByType('system');
|
||||
$image = $this->imageRepo->saveNew($logoFile, 'system', 0, null, 86);
|
||||
@@ -65,16 +75,21 @@ class SettingController extends Controller
|
||||
}
|
||||
|
||||
// Clear logo image if requested
|
||||
if ($request->get('app_logo_reset', null)) {
|
||||
if ($category === 'customization' && $request->get('app_logo_reset', null)) {
|
||||
$this->imageRepo->destroyByType('system');
|
||||
setting()->remove('app-logo');
|
||||
}
|
||||
|
||||
$section = $request->get('section', '');
|
||||
$this->logActivity(ActivityType::SETTINGS_UPDATE, $section);
|
||||
$this->logActivity(ActivityType::SETTINGS_UPDATE, $category);
|
||||
$this->showSuccessNotification(trans('settings.settings_save_success'));
|
||||
$redirectLocation = '/settings#' . $section;
|
||||
|
||||
return redirect(rtrim($redirectLocation, '#'));
|
||||
return redirect("/settings/{$category}");
|
||||
}
|
||||
|
||||
protected function ensureCategoryExists(string $category): void
|
||||
{
|
||||
if (!in_array($category, $this->settingCategories)) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class Localization
|
||||
/**
|
||||
* Array of right-to-left locales.
|
||||
*/
|
||||
protected $rtlLocales = ['ar', 'he'];
|
||||
protected $rtlLocales = ['ar', 'fa', 'he'];
|
||||
|
||||
/**
|
||||
* Map of BookStack locale names to best-estimate system locale names.
|
||||
@@ -29,6 +29,8 @@ class Localization
|
||||
'es' => 'es_ES',
|
||||
'es_AR' => 'es_AR',
|
||||
'et' => 'et_EE',
|
||||
'eu' => 'eu_ES',
|
||||
'fa' => 'fa_IR',
|
||||
'fr' => 'fr_FR',
|
||||
'he' => 'he_IL',
|
||||
'hr' => 'hr_HR',
|
||||
|
||||
@@ -8,20 +8,38 @@ class Request extends LaravelRequest
|
||||
{
|
||||
/**
|
||||
* Override the default request methods to get the scheme and host
|
||||
* to set the custom APP_URL, if set.
|
||||
* to directly use the custom APP_URL, if set.
|
||||
*
|
||||
* @return \Illuminate\Config\Repository|mixed|string
|
||||
* @return string
|
||||
*/
|
||||
public function getSchemeAndHttpHost()
|
||||
{
|
||||
$base = config('app.url', null);
|
||||
$appUrl = config('app.url', null);
|
||||
|
||||
if ($base) {
|
||||
$base = trim($base, '/');
|
||||
} else {
|
||||
$base = $this->getScheme() . '://' . $this->getHttpHost();
|
||||
if ($appUrl) {
|
||||
return implode('/', array_slice(explode('/', $appUrl), 0, 3));
|
||||
}
|
||||
|
||||
return $base;
|
||||
return parent::getSchemeAndHttpHost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default request methods to get the base URL
|
||||
* to directly use the custom APP_URL, if set.
|
||||
* The base URL never ends with a / but should start with one if not empty.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseUrl()
|
||||
{
|
||||
$appUrl = config('app.url', null);
|
||||
|
||||
if ($appUrl) {
|
||||
$parsedBaseUrl = rtrim(implode('/', array_slice(explode('/', $appUrl), 3)), '/');
|
||||
|
||||
return empty($parsedBaseUrl) ? '' : ('/' . $parsedBaseUrl);
|
||||
}
|
||||
|
||||
return parent::getBaseUrl();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +51,12 @@ class AppServiceProvider extends ServiceProvider
|
||||
// Allow longer string lengths after upgrade to utf8mb4
|
||||
Schema::defaultStringLength(191);
|
||||
|
||||
// Set morph-map due to namespace changes
|
||||
Relation::morphMap([
|
||||
'BookStack\\Bookshelf' => Bookshelf::class,
|
||||
'BookStack\\Book' => Book::class,
|
||||
'BookStack\\Chapter' => Chapter::class,
|
||||
'BookStack\\Page' => Page::class,
|
||||
// Set morph-map for our relations to friendlier aliases
|
||||
Relation::enforceMorphMap([
|
||||
'bookshelf' => Bookshelf::class,
|
||||
'book' => Book::class,
|
||||
'chapter' => Chapter::class,
|
||||
'page' => Page::class,
|
||||
]);
|
||||
|
||||
// View Composers
|
||||
|
||||
@@ -93,6 +93,8 @@ class ThemeEvents
|
||||
* @param string $event
|
||||
* @param \BookStack\Actions\Webhook $webhook
|
||||
* @param string|\BookStack\Interfaces\Loggable $detail
|
||||
* @param \BookStack\Auth\User $initiator
|
||||
* @param int $initiatedTime
|
||||
*/
|
||||
const WEBHOOK_CALL_BEFORE = 'webhook_call_before';
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
class AttachmentService
|
||||
{
|
||||
protected $fileSystem;
|
||||
protected FilesystemManager $fileSystem;
|
||||
|
||||
/**
|
||||
* AttachmentService constructor.
|
||||
@@ -73,6 +73,18 @@ class AttachmentService
|
||||
return $this->getStorageDisk()->get($this->adjustPathForStorageDisk($attachment->path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream an attachment from storage.
|
||||
*
|
||||
* @throws FileNotFoundException
|
||||
*
|
||||
* @return resource|null
|
||||
*/
|
||||
public function streamAttachmentFromStorage(Attachment $attachment)
|
||||
{
|
||||
return $this->getStorageDisk()->readStream($this->adjustPathForStorageDisk($attachment->path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new attachment upon user upload.
|
||||
*
|
||||
@@ -211,8 +223,6 @@ class AttachmentService
|
||||
*/
|
||||
protected function putFileInStorage(UploadedFile $uploadedFile): string
|
||||
{
|
||||
$attachmentData = file_get_contents($uploadedFile->getRealPath());
|
||||
|
||||
$storage = $this->getStorageDisk();
|
||||
$basePath = 'uploads/files/' . date('Y-m-M') . '/';
|
||||
|
||||
@@ -221,10 +231,11 @@ class AttachmentService
|
||||
$uploadFileName = Str::random(3) . $uploadFileName;
|
||||
}
|
||||
|
||||
$attachmentStream = fopen($uploadedFile->getRealPath(), 'r');
|
||||
$attachmentPath = $basePath . $uploadFileName;
|
||||
|
||||
try {
|
||||
$storage->put($this->adjustPathForStorageDisk($attachmentPath), $attachmentData);
|
||||
$storage->writeStream($this->adjustPathForStorageDisk($attachmentPath), $attachmentStream);
|
||||
} catch (Exception $e) {
|
||||
Log::error('Error when attempting file upload:' . $e->getMessage());
|
||||
|
||||
|
||||
@@ -148,7 +148,8 @@ class ImageRepo
|
||||
*/
|
||||
public function saveDrawing(string $base64Uri, int $uploadedTo): Image
|
||||
{
|
||||
$name = 'Drawing-' . user()->id . '-' . time() . '.png';
|
||||
$isSvg = strpos($base64Uri, 'data:image/svg+xml;') === 0;
|
||||
$name = 'Drawing-' . user()->id . '-' . time() . ($isSvg ? '.svg' : '.png');
|
||||
|
||||
return $this->imageService->saveNewFromBase64Uri($base64Uri, $name, 'drawio', $uploadedTo);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace BookStack\Uploads;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem as Storage;
|
||||
@@ -14,6 +15,7 @@ use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Intervention\Image\Exception\NotSupportedException;
|
||||
use Intervention\Image\Image as InterventionImage;
|
||||
use Intervention\Image\ImageManager;
|
||||
use League\Flysystem\Util;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
@@ -28,7 +30,7 @@ class ImageService
|
||||
protected $image;
|
||||
protected $fileSystem;
|
||||
|
||||
protected static $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
protected static $supportedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
|
||||
|
||||
/**
|
||||
* ImageService constructor.
|
||||
@@ -228,6 +230,14 @@ class ImageService
|
||||
return strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) === 'gif';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given image is an SVG image file.
|
||||
*/
|
||||
protected function isSvg(Image $image): bool
|
||||
{
|
||||
return strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) === 'svg';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given image and image data is apng.
|
||||
*/
|
||||
@@ -253,8 +263,8 @@ class ImageService
|
||||
*/
|
||||
public function getThumbnail(Image $image, ?int $width, ?int $height, bool $keepRatio = false): string
|
||||
{
|
||||
// Do not resize GIF images where we're not cropping
|
||||
if ($keepRatio && $this->isGif($image)) {
|
||||
// Do not resize GIF images where we're not cropping or SVG images.
|
||||
if (($keepRatio && $this->isGif($image)) || $this->isSvg($image)) {
|
||||
return $this->getPublicUrl($image->path);
|
||||
}
|
||||
|
||||
@@ -308,6 +318,8 @@ class ImageService
|
||||
throw new ImageUploadException(trans('errors.cannot_create_thumbs'));
|
||||
}
|
||||
|
||||
$this->orientImageToOriginalExif($thumb, $imageData);
|
||||
|
||||
if ($keepRatio) {
|
||||
$thumb->resize($width, $height, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
@@ -328,6 +340,49 @@ class ImageService
|
||||
return $thumbData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orientate the given intervention image based upon the given original image data.
|
||||
* Intervention does have an `orientate` method but the exif data it needs is lost before it
|
||||
* can be used (At least when created using binary string data) so we need to do some
|
||||
* implementation on our side to use the original image data.
|
||||
* Bulk of logic taken from: https://github.com/Intervention/image/blob/b734a4988b2148e7d10364b0609978a88d277536/src/Intervention/Image/Commands/OrientateCommand.php
|
||||
* Copyright (c) Oliver Vogel, MIT License.
|
||||
*/
|
||||
protected function orientImageToOriginalExif(InterventionImage $image, string $originalData): void
|
||||
{
|
||||
if (!extension_loaded('exif')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stream = Utils::streamFor($originalData)->detach();
|
||||
$exif = @exif_read_data($stream);
|
||||
$orientation = $exif ? ($exif['Orientation'] ?? null) : null;
|
||||
|
||||
switch ($orientation) {
|
||||
case 2:
|
||||
$image->flip();
|
||||
break;
|
||||
case 3:
|
||||
$image->rotate(180);
|
||||
break;
|
||||
case 4:
|
||||
$image->rotate(180)->flip();
|
||||
break;
|
||||
case 5:
|
||||
$image->rotate(270)->flip();
|
||||
break;
|
||||
case 6:
|
||||
$image->rotate(270);
|
||||
break;
|
||||
case 7:
|
||||
$image->rotate(90)->flip();
|
||||
break;
|
||||
case 8:
|
||||
$image->rotate(90);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw data content from an image.
|
||||
*
|
||||
|
||||
@@ -22,7 +22,7 @@ class CspService
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CSP headers for the application
|
||||
* Get the CSP headers for the application.
|
||||
*/
|
||||
public function getCspHeader(): string
|
||||
{
|
||||
@@ -86,6 +86,7 @@ class CspService
|
||||
{
|
||||
$iframeHosts = $this->getAllowedIframeHosts();
|
||||
array_unshift($iframeHosts, "'self'");
|
||||
|
||||
return 'frame-ancestors ' . implode(' ', $iframeHosts);
|
||||
}
|
||||
|
||||
@@ -97,6 +98,7 @@ class CspService
|
||||
{
|
||||
$iframeHosts = $this->getAllowedIframeSources();
|
||||
array_unshift($iframeHosts, "'self'");
|
||||
|
||||
return 'frame-src ' . implode(' ', $iframeHosts);
|
||||
}
|
||||
|
||||
|
||||
847
composer.lock
generated
847
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddEditorChangeFieldAndPermission extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// Add the new 'editor' column to the pages table
|
||||
Schema::table('pages', function (Blueprint $table) {
|
||||
$table->string('editor', 50)->default('');
|
||||
});
|
||||
|
||||
// Populate the new 'editor' column
|
||||
// We set it to 'markdown' for pages currently with markdown content
|
||||
DB::table('pages')->where('markdown', '!=', '')->update(['editor' => 'markdown']);
|
||||
// We set it to 'wysiwyg' where we have HTML but no markdown
|
||||
DB::table('pages')->where('markdown', '=', '')
|
||||
->where('html', '!=', '')
|
||||
->update(['editor' => 'wysiwyg']);
|
||||
|
||||
// Give the admin user permission to change the editor
|
||||
$adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id;
|
||||
|
||||
$permissionId = DB::table('role_permissions')->insertGetId([
|
||||
'name' => 'editor-change',
|
||||
'display_name' => 'Change page editor',
|
||||
'created_at' => Carbon::now()->toDateTimeString(),
|
||||
'updated_at' => Carbon::now()->toDateTimeString(),
|
||||
]);
|
||||
|
||||
DB::table('permission_role')->insert([
|
||||
'role_id' => $adminRoleId,
|
||||
'permission_id' => $permissionId,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
// Drop the new column from the pages table
|
||||
Schema::table('pages', function (Blueprint $table) {
|
||||
$table->dropColumn('editor');
|
||||
});
|
||||
|
||||
// Remove traces of the role permission
|
||||
DB::table('role_permissions')->where('name', '=', 'editor-change')->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class UpdatePolymorphicTypes extends Migration
|
||||
{
|
||||
/**
|
||||
* Mapping of old polymorphic types to new simpler values.
|
||||
*/
|
||||
protected $changeMap = [
|
||||
'BookStack\\Bookshelf' => 'bookshelf',
|
||||
'BookStack\\Book' => 'book',
|
||||
'BookStack\\Chapter' => 'chapter',
|
||||
'BookStack\\Page' => 'page',
|
||||
];
|
||||
|
||||
/**
|
||||
* Mapping of tables and columns that contain polymorphic types.
|
||||
*/
|
||||
protected $columnsByTable = [
|
||||
'activities' => 'entity_type',
|
||||
'comments' => 'entity_type',
|
||||
'deletions' => 'deletable_type',
|
||||
'entity_permissions' => 'restrictable_type',
|
||||
'favourites' => 'favouritable_type',
|
||||
'joint_permissions' => 'entity_type',
|
||||
'search_terms' => 'entity_type',
|
||||
'tags' => 'entity_type',
|
||||
'views' => 'viewable_type',
|
||||
];
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
foreach ($this->columnsByTable as $table => $column) {
|
||||
foreach ($this->changeMap as $oldVal => $newVal) {
|
||||
DB::table($table)
|
||||
->where([$column => $oldVal])
|
||||
->update([$column => $newVal]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
foreach ($this->columnsByTable as $table => $column) {
|
||||
foreach ($this->changeMap as $oldVal => $newVal) {
|
||||
DB::table($table)
|
||||
->where([$column => $newVal])
|
||||
->update([$column => $oldVal]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
dev/api/responses/recycle-bin-destroy.json
Normal file
3
dev/api/responses/recycle-bin-destroy.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"delete_count": 2
|
||||
}
|
||||
64
dev/api/responses/recycle-bin-list.json
Normal file
64
dev/api/responses/recycle-bin-list.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 18,
|
||||
"deleted_by": 1,
|
||||
"created_at": "2022-04-20T12:57:46.000000Z",
|
||||
"updated_at": "2022-04-20T12:57:46.000000Z",
|
||||
"deletable_type": "page",
|
||||
"deletable_id": 2582,
|
||||
"deletable": {
|
||||
"id": 2582,
|
||||
"book_id": 25,
|
||||
"chapter_id": 0,
|
||||
"name": "A Wonderful Page",
|
||||
"slug": "a-wonderful-page",
|
||||
"priority": 9,
|
||||
"created_at": "2022-02-08T00:44:45.000000Z",
|
||||
"updated_at": "2022-04-20T12:57:46.000000Z",
|
||||
"created_by": 1,
|
||||
"updated_by": 1,
|
||||
"draft": false,
|
||||
"revision_count": 1,
|
||||
"template": false,
|
||||
"owned_by": 1,
|
||||
"editor": "wysiwyg",
|
||||
"book_slug": "a-great-book",
|
||||
"parent": {
|
||||
"id": 25,
|
||||
"name": "A Great Book",
|
||||
"slug": "a-great-book",
|
||||
"description": "",
|
||||
"created_at": "2022-01-24T16:14:28.000000Z",
|
||||
"updated_at": "2022-03-06T15:14:50.000000Z",
|
||||
"created_by": 1,
|
||||
"updated_by": 1,
|
||||
"owned_by": 1,
|
||||
"type": "book"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"deleted_by": 1,
|
||||
"created_at": "2022-04-25T16:07:46.000000Z",
|
||||
"updated_at": "2022-04-25T16:07:46.000000Z",
|
||||
"deletable_type": "book",
|
||||
"deletable_id": 13,
|
||||
"deletable": {
|
||||
"id": 13,
|
||||
"name": "A Big Book!",
|
||||
"slug": "a-big-book",
|
||||
"description": "This is a very large book with loads of cool stuff in it!",
|
||||
"created_at": "2021-11-08T11:26:43.000000Z",
|
||||
"updated_at": "2022-04-25T16:07:47.000000Z",
|
||||
"created_by": 27,
|
||||
"updated_by": 1,
|
||||
"owned_by": 1,
|
||||
"pages_count": 208,
|
||||
"chapters_count": 50
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
}
|
||||
3
dev/api/responses/recycle-bin-restore.json
Normal file
3
dev/api/responses/recycle-bin-restore.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"restore_count": 2
|
||||
}
|
||||
32
dev/build/esbuild.js
Normal file
32
dev/build/esbuild.js
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const esbuild = require('esbuild');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Check if we're building for production
|
||||
// (Set via passing `production` as first argument)
|
||||
const isProd = process.argv[2] === 'production';
|
||||
|
||||
// Gather our input files
|
||||
const jsInDir = path.join(__dirname, '../../resources/js');
|
||||
const jsInDirFiles = fs.readdirSync(jsInDir, 'utf8');
|
||||
const entryFiles = jsInDirFiles
|
||||
.filter(f => f.endsWith('.js') || f.endsWith('.mjs'))
|
||||
.map(f => path.join(jsInDir, f));
|
||||
|
||||
// Locate our output directory
|
||||
const outDir = path.join(__dirname, '../../public/dist');
|
||||
|
||||
// Build via esbuild
|
||||
esbuild.build({
|
||||
bundle: true,
|
||||
entryPoints: entryFiles,
|
||||
outdir: outDir,
|
||||
sourcemap: true,
|
||||
target: 'es2020',
|
||||
mainFields: ['module', 'main'],
|
||||
format: 'esm',
|
||||
minify: isProd,
|
||||
logLevel: "info",
|
||||
}).catch(() => process.exit(1));
|
||||
384
package-lock.json
generated
384
package-lock.json
generated
@@ -4,28 +4,27 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bookstack",
|
||||
"dependencies": {
|
||||
"clipboard": "^2.0.10",
|
||||
"codemirror": "^5.65.2",
|
||||
"dropzone": "^5.9.3",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"sortablejs": "^1.14.0"
|
||||
"sortablejs": "^1.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chokidar-cli": "^3.0",
|
||||
"esbuild": "0.14.23",
|
||||
"esbuild": "0.14.36",
|
||||
"livereload": "^0.9.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"punycode": "^2.1.1",
|
||||
"sass": "^1.49.8"
|
||||
"sass": "^1.50.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
|
||||
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -342,9 +341,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.23.tgz",
|
||||
"integrity": "sha512-XjnIcZ9KB6lfonCa+jRguXyRYcldmkyZ99ieDksqW/C8bnyEX299yA4QH2XcgijCgaddEZePPTgvx/2imsq7Ig==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.36.tgz",
|
||||
"integrity": "sha512-HhFHPiRXGYOCRlrhpiVDYKcFJRdO0sBElZ668M4lh2ER0YgnkLxECuFe7uWCf23FrcLc59Pqr7dHkTqmRPDHmw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
@@ -354,31 +353,48 @@
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"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-64": "0.14.36",
|
||||
"esbuild-android-arm64": "0.14.36",
|
||||
"esbuild-darwin-64": "0.14.36",
|
||||
"esbuild-darwin-arm64": "0.14.36",
|
||||
"esbuild-freebsd-64": "0.14.36",
|
||||
"esbuild-freebsd-arm64": "0.14.36",
|
||||
"esbuild-linux-32": "0.14.36",
|
||||
"esbuild-linux-64": "0.14.36",
|
||||
"esbuild-linux-arm": "0.14.36",
|
||||
"esbuild-linux-arm64": "0.14.36",
|
||||
"esbuild-linux-mips64le": "0.14.36",
|
||||
"esbuild-linux-ppc64le": "0.14.36",
|
||||
"esbuild-linux-riscv64": "0.14.36",
|
||||
"esbuild-linux-s390x": "0.14.36",
|
||||
"esbuild-netbsd-64": "0.14.36",
|
||||
"esbuild-openbsd-64": "0.14.36",
|
||||
"esbuild-sunos-64": "0.14.36",
|
||||
"esbuild-windows-32": "0.14.36",
|
||||
"esbuild-windows-64": "0.14.36",
|
||||
"esbuild-windows-arm64": "0.14.36"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.36.tgz",
|
||||
"integrity": "sha512-jwpBhF1jmo0tVCYC/ORzVN+hyVcNZUWuozGcLHfod0RJCedTDTvR4nwlTXdx1gtncDqjk33itjO+27OZHbiavw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-arm64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-k9sXem++mINrZty1v4FVt6nC5BQCFG4K2geCIUUqHNlTdFnuvcqsY7prcKZLFhqVC1rbcJAr9VSUGFL/vD4vsw==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-/hYkyFe7x7Yapmfv4X/tBmyKnggUmdQmlvZ8ZlBnV4+PjisrEhAvC3yWpURuD9XoB8Wa1d5dGkTsF53pIvpjsg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -392,9 +408,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.36.tgz",
|
||||
"integrity": "sha512-kkl6qmV0dTpyIMKagluzYqlc1vO0ecgpviK/7jwPbRDEv5fejRTaBBEE2KxEQbTHcLhiiDbhG7d5UybZWo/1zQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -408,9 +424,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-arm64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-q8fY4r2Sx6P0Pr3VUm//eFYKVk07C5MHcEinU1BjyFnuYz4IxR/03uBbDwluR6ILIHnZTE7AkTUWIdidRi1Jjw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -424,9 +440,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.23.tgz",
|
||||
"integrity": "sha512-/1xiTjoLuQ+LlbfjJdKkX45qK/M7ARrbLmyf7x3JhyQGMjcxRYVR6Dw81uH3qlMHwT4cfLW4aEVBhP1aNV7VsA==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-Hn8AYuxXXRptybPqoMkga4HRFE7/XmhtlQjXFHoAIhKUPPMeJH35GYEUWGbjteai9FLFvBAjEAlwEtSGxnqWww==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -440,9 +456,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-arm64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-uyPqBU/Zcp6yEAZS4LKj5jEE0q2s4HmlMBIPzbW6cTunZ8cyvjG6YWpIZXb1KK3KTJDe62ltCrk3VzmWHp+iLg==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-S3C0attylLLRiCcHiJd036eDEMOY32+h8P+jJ3kTcfhJANNjP0TNBNL30TZmEdOSx/820HJFgRrqpNAvTbjnDA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -456,9 +472,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-32": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.36.tgz",
|
||||
"integrity": "sha512-Eh9OkyTrEZn9WGO4xkI3OPPpUX7p/3QYvdG0lL4rfr73Ap2HAr6D9lP59VMF64Ex01LhHSXwIsFG/8AQjh6eNw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -472,9 +488,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.36.tgz",
|
||||
"integrity": "sha512-vFVFS5ve7PuwlfgoWNyRccGDi2QTNkQo/2k5U5ttVD0jRFaMlc8UQee708fOZA6zTCDy5RWsT5MJw3sl2X6KDg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -488,9 +504,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.36.tgz",
|
||||
"integrity": "sha512-NhgU4n+NCsYgt7Hy61PCquEz5aevI6VjQvxwBxtxrooXsxt5b2xtOUXYZe04JxqQo+XZk3d1gcr7pbV9MAQ/Lg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -504,9 +520,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-c4MLOIByNHR55n3KoYf9hYDfBRghMjOiHLaoYLhkQkIabb452RWi+HsNgB41sUpSlOAqfpqKPFNg7VrxL3UX9g==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-24Vq1M7FdpSmaTYuu1w0Hdhiqkbto1I5Pjyi+4Cdw5fJKGlwQuw+hWynTcRI/cOZxBcBpP21gND7W27gHAiftw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -520,9 +536,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-mips64le": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.23.tgz",
|
||||
"integrity": "sha512-kHKyKRIAedYhKug2EJpyJxOUj3VYuamOVA1pY7EimoFPzaF3NeY7e4cFBAISC/Av0/tiV0xlFCt9q0HJ68IBIw==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.36.tgz",
|
||||
"integrity": "sha512-hZUeTXvppJN+5rEz2EjsOFM9F1bZt7/d2FUM1lmQo//rXh1RTFYzhC0txn7WV0/jCC7SvrGRaRz0NMsRPf8SIA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -536,9 +552,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-ppc64le": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.36.tgz",
|
||||
"integrity": "sha512-1Bg3QgzZjO+QtPhP9VeIBhAduHEc2kzU43MzBnMwpLSZ890azr4/A9Dganun8nsqD/1TBcqhId0z4mFDO8FAvg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -552,9 +568,9 @@
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.36.tgz",
|
||||
"integrity": "sha512-dOE5pt3cOdqEhaufDRzNCHf5BSwxgygVak9UR7PH7KPVHwSTDAZHDoEjblxLqjJYpc5XaU9+gKJ9F8mp9r5I4A==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -568,9 +584,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-s390x": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.36.tgz",
|
||||
"integrity": "sha512-g4FMdh//BBGTfVHjF6MO7Cz8gqRoDPzXWxRvWkJoGroKA18G9m0wddvPbEqcQf5Tbt2vSc1CIgag7cXwTmoTXg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -584,9 +600,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-netbsd-64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-UB2bVImxkWk4vjnP62ehFNZ73lQY1xcnL5ZNYF3x0AG+j8HgdkNF05v67YJdCIuUJpBuTyCK8LORCYo9onSW+A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -600,9 +616,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-openbsd-64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.23.tgz",
|
||||
"integrity": "sha512-uYYNqbVR+i7k8ojP/oIROAHO9lATLN7H2QeXKt2H310Fc8FJj4y3Wce6hx0VgnJ4k1JDrgbbiXM8rbEgQyg8KA==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-NvGB2Chf8GxuleXRGk8e9zD3aSdRO5kLt9coTQbCg7WMGXeX471sBgh4kSg8pjx0yTXRt0MlrUDnjVYnetyivg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -616,9 +632,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.23.tgz",
|
||||
"integrity": "sha512-hAzeBeET0+SbScknPzS2LBY6FVDpgE+CsHSpe6CEoR51PApdn2IB0SyJX7vGelXzlyrnorM4CAsRyb9Qev4h9g==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.36.tgz",
|
||||
"integrity": "sha512-VkUZS5ftTSjhRjuRLp+v78auMO3PZBXu6xl4ajomGenEm2/rGuWlhFSjB7YbBNErOchj51Jb2OK8lKAo8qdmsQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -632,9 +648,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-32": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.23.tgz",
|
||||
"integrity": "sha512-Kttmi3JnohdaREbk6o9e25kieJR379TsEWF0l39PQVHXq3FR6sFKtVPgY8wk055o6IB+rllrzLnbqOw/UV60EA==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.36.tgz",
|
||||
"integrity": "sha512-bIar+A6hdytJjZrDxfMBUSEHHLfx3ynoEZXx/39nxy86pX/w249WZm8Bm0dtOAByAf4Z6qV0LsnTIJHiIqbw0w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -648,9 +664,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.36.tgz",
|
||||
"integrity": "sha512-+p4MuRZekVChAeueT1Y9LGkxrT5x7YYJxYE8ZOTcEfeUUN43vktSn6hUNsvxzzATrSgq5QqRdllkVBxWZg7KqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -664,9 +680,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-arm64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-fBB4WlDqV1m18EF/aheGYQkQZHfPHiHJSBYzXIo8yKehek+0BtBwo/4PNwKGJ5T0YK0oc8pBKjgwPbzSrPLb+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1504,9 +1520,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.49.8",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz",
|
||||
"integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==",
|
||||
"version": "1.50.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
|
||||
"integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
@@ -1582,9 +1598,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
|
||||
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w=="
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
@@ -1870,9 +1886,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
|
||||
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
@@ -2130,162 +2146,170 @@
|
||||
}
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.23.tgz",
|
||||
"integrity": "sha512-XjnIcZ9KB6lfonCa+jRguXyRYcldmkyZ99ieDksqW/C8bnyEX299yA4QH2XcgijCgaddEZePPTgvx/2imsq7Ig==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.36.tgz",
|
||||
"integrity": "sha512-HhFHPiRXGYOCRlrhpiVDYKcFJRdO0sBElZ668M4lh2ER0YgnkLxECuFe7uWCf23FrcLc59Pqr7dHkTqmRPDHmw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"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-64": "0.14.36",
|
||||
"esbuild-android-arm64": "0.14.36",
|
||||
"esbuild-darwin-64": "0.14.36",
|
||||
"esbuild-darwin-arm64": "0.14.36",
|
||||
"esbuild-freebsd-64": "0.14.36",
|
||||
"esbuild-freebsd-arm64": "0.14.36",
|
||||
"esbuild-linux-32": "0.14.36",
|
||||
"esbuild-linux-64": "0.14.36",
|
||||
"esbuild-linux-arm": "0.14.36",
|
||||
"esbuild-linux-arm64": "0.14.36",
|
||||
"esbuild-linux-mips64le": "0.14.36",
|
||||
"esbuild-linux-ppc64le": "0.14.36",
|
||||
"esbuild-linux-riscv64": "0.14.36",
|
||||
"esbuild-linux-s390x": "0.14.36",
|
||||
"esbuild-netbsd-64": "0.14.36",
|
||||
"esbuild-openbsd-64": "0.14.36",
|
||||
"esbuild-sunos-64": "0.14.36",
|
||||
"esbuild-windows-32": "0.14.36",
|
||||
"esbuild-windows-64": "0.14.36",
|
||||
"esbuild-windows-arm64": "0.14.36"
|
||||
}
|
||||
},
|
||||
"esbuild-android-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.36.tgz",
|
||||
"integrity": "sha512-jwpBhF1jmo0tVCYC/ORzVN+hyVcNZUWuozGcLHfod0RJCedTDTvR4nwlTXdx1gtncDqjk33itjO+27OZHbiavw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-android-arm64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-k9sXem++mINrZty1v4FVt6nC5BQCFG4K2geCIUUqHNlTdFnuvcqsY7prcKZLFhqVC1rbcJAr9VSUGFL/vD4vsw==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-/hYkyFe7x7Yapmfv4X/tBmyKnggUmdQmlvZ8ZlBnV4+PjisrEhAvC3yWpURuD9XoB8Wa1d5dGkTsF53pIvpjsg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.36.tgz",
|
||||
"integrity": "sha512-kkl6qmV0dTpyIMKagluzYqlc1vO0ecgpviK/7jwPbRDEv5fejRTaBBEE2KxEQbTHcLhiiDbhG7d5UybZWo/1zQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-arm64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-q8fY4r2Sx6P0Pr3VUm//eFYKVk07C5MHcEinU1BjyFnuYz4IxR/03uBbDwluR6ILIHnZTE7AkTUWIdidRi1Jjw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.23.tgz",
|
||||
"integrity": "sha512-/1xiTjoLuQ+LlbfjJdKkX45qK/M7ARrbLmyf7x3JhyQGMjcxRYVR6Dw81uH3qlMHwT4cfLW4aEVBhP1aNV7VsA==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-Hn8AYuxXXRptybPqoMkga4HRFE7/XmhtlQjXFHoAIhKUPPMeJH35GYEUWGbjteai9FLFvBAjEAlwEtSGxnqWww==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-arm64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-uyPqBU/Zcp6yEAZS4LKj5jEE0q2s4HmlMBIPzbW6cTunZ8cyvjG6YWpIZXb1KK3KTJDe62ltCrk3VzmWHp+iLg==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-S3C0attylLLRiCcHiJd036eDEMOY32+h8P+jJ3kTcfhJANNjP0TNBNL30TZmEdOSx/820HJFgRrqpNAvTbjnDA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-32": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.36.tgz",
|
||||
"integrity": "sha512-Eh9OkyTrEZn9WGO4xkI3OPPpUX7p/3QYvdG0lL4rfr73Ap2HAr6D9lP59VMF64Ex01LhHSXwIsFG/8AQjh6eNw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.36.tgz",
|
||||
"integrity": "sha512-vFVFS5ve7PuwlfgoWNyRccGDi2QTNkQo/2k5U5ttVD0jRFaMlc8UQee708fOZA6zTCDy5RWsT5MJw3sl2X6KDg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.36.tgz",
|
||||
"integrity": "sha512-NhgU4n+NCsYgt7Hy61PCquEz5aevI6VjQvxwBxtxrooXsxt5b2xtOUXYZe04JxqQo+XZk3d1gcr7pbV9MAQ/Lg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.23.tgz",
|
||||
"integrity": "sha512-c4MLOIByNHR55n3KoYf9hYDfBRghMjOiHLaoYLhkQkIabb452RWi+HsNgB41sUpSlOAqfpqKPFNg7VrxL3UX9g==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-24Vq1M7FdpSmaTYuu1w0Hdhiqkbto1I5Pjyi+4Cdw5fJKGlwQuw+hWynTcRI/cOZxBcBpP21gND7W27gHAiftw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-mips64le": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.23.tgz",
|
||||
"integrity": "sha512-kHKyKRIAedYhKug2EJpyJxOUj3VYuamOVA1pY7EimoFPzaF3NeY7e4cFBAISC/Av0/tiV0xlFCt9q0HJ68IBIw==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.36.tgz",
|
||||
"integrity": "sha512-hZUeTXvppJN+5rEz2EjsOFM9F1bZt7/d2FUM1lmQo//rXh1RTFYzhC0txn7WV0/jCC7SvrGRaRz0NMsRPf8SIA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-ppc64le": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.36.tgz",
|
||||
"integrity": "sha512-1Bg3QgzZjO+QtPhP9VeIBhAduHEc2kzU43MzBnMwpLSZ890azr4/A9Dganun8nsqD/1TBcqhId0z4mFDO8FAvg==",
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.36.tgz",
|
||||
"integrity": "sha512-dOE5pt3cOdqEhaufDRzNCHf5BSwxgygVak9UR7PH7KPVHwSTDAZHDoEjblxLqjJYpc5XaU9+gKJ9F8mp9r5I4A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-s390x": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.36.tgz",
|
||||
"integrity": "sha512-g4FMdh//BBGTfVHjF6MO7Cz8gqRoDPzXWxRvWkJoGroKA18G9m0wddvPbEqcQf5Tbt2vSc1CIgag7cXwTmoTXg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-netbsd-64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-UB2bVImxkWk4vjnP62ehFNZ73lQY1xcnL5ZNYF3x0AG+j8HgdkNF05v67YJdCIuUJpBuTyCK8LORCYo9onSW+A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-openbsd-64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.23.tgz",
|
||||
"integrity": "sha512-uYYNqbVR+i7k8ojP/oIROAHO9lATLN7H2QeXKt2H310Fc8FJj4y3Wce6hx0VgnJ4k1JDrgbbiXM8rbEgQyg8KA==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-NvGB2Chf8GxuleXRGk8e9zD3aSdRO5kLt9coTQbCg7WMGXeX471sBgh4kSg8pjx0yTXRt0MlrUDnjVYnetyivg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-sunos-64": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.23.tgz",
|
||||
"integrity": "sha512-hAzeBeET0+SbScknPzS2LBY6FVDpgE+CsHSpe6CEoR51PApdn2IB0SyJX7vGelXzlyrnorM4CAsRyb9Qev4h9g==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.36.tgz",
|
||||
"integrity": "sha512-VkUZS5ftTSjhRjuRLp+v78auMO3PZBXu6xl4ajomGenEm2/rGuWlhFSjB7YbBNErOchj51Jb2OK8lKAo8qdmsQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-32": {
|
||||
"version": "0.14.23",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.23.tgz",
|
||||
"integrity": "sha512-Kttmi3JnohdaREbk6o9e25kieJR379TsEWF0l39PQVHXq3FR6sFKtVPgY8wk055o6IB+rllrzLnbqOw/UV60EA==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.36.tgz",
|
||||
"integrity": "sha512-bIar+A6hdytJjZrDxfMBUSEHHLfx3ynoEZXx/39nxy86pX/w249WZm8Bm0dtOAByAf4Z6qV0LsnTIJHiIqbw0w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.36.tgz",
|
||||
"integrity": "sha512-+p4MuRZekVChAeueT1Y9LGkxrT5x7YYJxYE8ZOTcEfeUUN43vktSn6hUNsvxzzATrSgq5QqRdllkVBxWZg7KqQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-arm64": {
|
||||
"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==",
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-fBB4WlDqV1m18EF/aheGYQkQZHfPHiHJSBYzXIo8yKehek+0BtBwo/4PNwKGJ5T0YK0oc8pBKjgwPbzSrPLb+Q==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@@ -2886,9 +2910,9 @@
|
||||
}
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.49.8",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz",
|
||||
"integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==",
|
||||
"version": "1.50.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
|
||||
"integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
@@ -2946,9 +2970,9 @@
|
||||
}
|
||||
},
|
||||
"sortablejs": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
|
||||
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w=="
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "1.0.2",
|
||||
|
||||
10
package.json
10
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/*.{js,mjs} --outdir=public/dist/ --sourcemap --target=es2020 --main-fields=module,main --format=esm",
|
||||
"build:js:dev": "node dev/build/esbuild.js",
|
||||
"build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"",
|
||||
"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:js:production": "node dev/build/esbuild.js production",
|
||||
"build": "npm-run-all --parallel build:*:dev",
|
||||
"production": "npm-run-all --parallel build:*:production",
|
||||
"dev": "npm-run-all --parallel watch livereload",
|
||||
@@ -16,11 +16,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"chokidar-cli": "^3.0",
|
||||
"esbuild": "0.14.23",
|
||||
"esbuild": "0.14.36",
|
||||
"livereload": "^0.9.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"punycode": "^2.1.1",
|
||||
"sass": "^1.49.8"
|
||||
"sass": "^1.50.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"clipboard": "^2.0.10",
|
||||
@@ -28,6 +28,6 @@
|
||||
"dropzone": "^5.9.3",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"sortablejs": "^1.14.0"
|
||||
"sortablejs": "^1.15.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ parameters:
|
||||
# The level 8 is the highest level
|
||||
level: 1
|
||||
|
||||
phpVersion: 70300
|
||||
phpVersion: 70400
|
||||
|
||||
bootstrapFiles:
|
||||
- bootstrap/phpstan.php
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
<server name="AVATAR_URL" value=""/>
|
||||
<server name="LDAP_START_TLS" value="false"/>
|
||||
<server name="LDAP_VERSION" value="3"/>
|
||||
<server name="LDAP_DUMP_USER_DETAILS" value="false"/>
|
||||
<server name="LDAP_DUMP_USER_GROUPS" value="false"/>
|
||||
<server name="SESSION_SECURE_COOKIE" value="null"/>
|
||||
<server name="STORAGE_TYPE" value="local"/>
|
||||
<server name="STORAGE_ATTACHMENT_TYPE" value="local"/>
|
||||
|
||||
1
resources/icons/palette.svg
Normal file
1
resources/icons/palette.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,2C6.49,2,2,6.49,2,12s4.49,10,10,10c1.38,0,2.5-1.12,2.5-2.5c0-0.61-0.23-1.2-0.64-1.67c-0.08-0.1-0.13-0.21-0.13-0.33 c0-0.28,0.22-0.5,0.5-0.5H16c3.31,0,6-2.69,6-6C22,6.04,17.51,2,12,2z M17.5,13c-0.83,0-1.5-0.67-1.5-1.5c0-0.83,0.67-1.5,1.5-1.5 s1.5,0.67,1.5,1.5C19,12.33,18.33,13,17.5,13z M14.5,9C13.67,9,13,8.33,13,7.5C13,6.67,13.67,6,14.5,6S16,6.67,16,7.5 C16,8.33,15.33,9,14.5,9z M5,11.5C5,10.67,5.67,10,6.5,10S8,10.67,8,11.5C8,12.33,7.33,13,6.5,13S5,12.33,5,11.5z M11,7.5 C11,8.33,10.33,9,9.5,9S8,8.33,8,7.5C8,6.67,8.67,6,9.5,6S11,6.67,11,7.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 626 B |
1
resources/icons/swap-horizontal.svg
Normal file
1
resources/icons/swap-horizontal.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.99 16H14v-2H6.99v-3L3 15l3.99 4ZM21 9l-3.99-4v3H10v2h7.01v3z"/></svg>
|
||||
|
After Width: | Height: | Size: 141 B |
@@ -131,7 +131,7 @@ class AutoSuggest {
|
||||
return this.hideSuggestions();
|
||||
}
|
||||
|
||||
this.list.innerHTML = suggestions.map(value => `<li><button type="button">${escapeHtml(value)}</button></li>`).join('');
|
||||
this.list.innerHTML = suggestions.map(value => `<li><button type="button" class="text-item">${escapeHtml(value)}</button></li>`).join('');
|
||||
this.list.style.display = 'block';
|
||||
for (const button of this.list.querySelectorAll('button')) {
|
||||
button.addEventListener('blur', this.hideSuggestionsIfFocusedLost.bind(this));
|
||||
|
||||
@@ -96,7 +96,7 @@ class CodeEditor {
|
||||
this.historyDropDown.classList.toggle('hidden', historyKeys.length === 0);
|
||||
this.historyList.innerHTML = historyKeys.map(key => {
|
||||
const localTime = (new Date(parseInt(key))).toLocaleTimeString();
|
||||
return `<li><button type="button" data-time="${key}">${localTime}</button></li>`;
|
||||
return `<li><button type="button" data-time="${key}" class="text-item">${localTime}</button></li>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
|
||||
52
resources/js/components/confirm-dialog.js
Normal file
52
resources/js/components/confirm-dialog.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import {onSelect} from "../services/dom";
|
||||
|
||||
/**
|
||||
* Custom equivalent of window.confirm() using our popup component.
|
||||
* Is promise based so can be used like so:
|
||||
* `const result = await dialog.show()`
|
||||
* @extends {Component}
|
||||
*/
|
||||
class ConfirmDialog {
|
||||
|
||||
setup() {
|
||||
this.container = this.$el;
|
||||
this.confirmButton = this.$refs.confirm;
|
||||
|
||||
this.res = null;
|
||||
|
||||
onSelect(this.confirmButton, () => {
|
||||
this.sendResult(true);
|
||||
this.getPopup().hide();
|
||||
});
|
||||
}
|
||||
|
||||
show() {
|
||||
this.getPopup().show(null, () => {
|
||||
this.sendResult(false);
|
||||
});
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
this.res = res;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Popup}
|
||||
*/
|
||||
getPopup() {
|
||||
return this.container.components.popup;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Boolean} result
|
||||
*/
|
||||
sendResult(result) {
|
||||
if (this.res) {
|
||||
this.res(result)
|
||||
this.res = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ConfirmDialog;
|
||||
@@ -10,6 +10,7 @@ import chapterToggle from "./chapter-toggle.js"
|
||||
import codeEditor from "./code-editor.js"
|
||||
import codeHighlighter from "./code-highlighter.js"
|
||||
import collapsible from "./collapsible.js"
|
||||
import confirmDialog from "./confirm-dialog"
|
||||
import customCheckbox from "./custom-checkbox.js"
|
||||
import detailsHighlighter from "./details-highlighter.js"
|
||||
import dropdown from "./dropdown.js"
|
||||
@@ -26,7 +27,6 @@ import headerMobileToggle from "./header-mobile-toggle.js"
|
||||
import homepageControl from "./homepage-control.js"
|
||||
import imageManager from "./image-manager.js"
|
||||
import imagePicker from "./image-picker.js"
|
||||
import index from "./index.js"
|
||||
import listSortControl from "./list-sort-control.js"
|
||||
import markdownEditor from "./markdown-editor.js"
|
||||
import newUserPassword from "./new-user-password.js"
|
||||
@@ -66,6 +66,7 @@ const componentMapping = {
|
||||
"code-editor": codeEditor,
|
||||
"code-highlighter": codeHighlighter,
|
||||
"collapsible": collapsible,
|
||||
"confirm-dialog": confirmDialog,
|
||||
"custom-checkbox": customCheckbox,
|
||||
"details-highlighter": detailsHighlighter,
|
||||
"dropdown": dropdown,
|
||||
@@ -82,7 +83,6 @@ const componentMapping = {
|
||||
"homepage-control": homepageControl,
|
||||
"image-manager": imageManager,
|
||||
"image-picker": imagePicker,
|
||||
"index": index,
|
||||
"list-sort-control": listSortControl,
|
||||
"markdown-editor": markdownEditor,
|
||||
"new-user-password": newUserPassword,
|
||||
|
||||
@@ -18,10 +18,8 @@ class MarkdownEditor {
|
||||
this.markdown = new MarkdownIt({html: true});
|
||||
this.markdown.use(mdTasksLists, {label: true});
|
||||
|
||||
this.display = this.elem.querySelector('.markdown-display');
|
||||
|
||||
this.displayStylesLoaded = false;
|
||||
this.input = this.elem.querySelector('textarea');
|
||||
this.display = this.$refs.display;
|
||||
this.input = this.$refs.input;
|
||||
|
||||
this.cm = null;
|
||||
this.Code = null;
|
||||
@@ -32,23 +30,13 @@ class MarkdownEditor {
|
||||
});
|
||||
|
||||
this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
|
||||
|
||||
const displayLoad = () => {
|
||||
this.displayDoc = this.display.contentDocument;
|
||||
this.init(cmLoadPromise);
|
||||
};
|
||||
|
||||
if (this.display.contentDocument.readyState === 'complete') {
|
||||
displayLoad();
|
||||
} else {
|
||||
this.display.addEventListener('load', displayLoad.bind(this));
|
||||
}
|
||||
|
||||
window.$events.emitPublic(this.elem, 'editor-markdown::setup', {
|
||||
markdownIt: this.markdown,
|
||||
displayEl: this.display,
|
||||
codeMirrorInstance: this.cm,
|
||||
});
|
||||
|
||||
this.init(cmLoadPromise);
|
||||
}
|
||||
|
||||
init(cmLoadPromise) {
|
||||
@@ -56,17 +44,17 @@ class MarkdownEditor {
|
||||
let lastClick = 0;
|
||||
|
||||
// Prevent markdown display link click redirect
|
||||
this.displayDoc.addEventListener('click', event => {
|
||||
let isDblClick = Date.now() - lastClick < 300;
|
||||
this.display.addEventListener('click', event => {
|
||||
const isDblClick = Date.now() - lastClick < 300;
|
||||
|
||||
let link = event.target.closest('a');
|
||||
const link = event.target.closest('a');
|
||||
if (link !== null) {
|
||||
event.preventDefault();
|
||||
window.open(link.getAttribute('href'));
|
||||
return;
|
||||
}
|
||||
|
||||
let drawing = event.target.closest('[drawio-diagram]');
|
||||
const drawing = event.target.closest('[drawio-diagram]');
|
||||
if (drawing !== null && isDblClick) {
|
||||
this.actionEditDrawing(drawing);
|
||||
return;
|
||||
@@ -77,10 +65,10 @@ class MarkdownEditor {
|
||||
|
||||
// Button actions
|
||||
this.elem.addEventListener('click', event => {
|
||||
let button = event.target.closest('button[data-action]');
|
||||
const button = event.target.closest('button[data-action]');
|
||||
if (button === null) return;
|
||||
|
||||
let action = button.getAttribute('data-action');
|
||||
const action = button.getAttribute('data-action');
|
||||
if (action === 'insertImage') this.actionInsertImage();
|
||||
if (action === 'insertLink') this.actionShowLinkSelector();
|
||||
if (action === 'insertDrawing' && (event.ctrlKey || event.metaKey)) {
|
||||
@@ -132,35 +120,11 @@ class MarkdownEditor {
|
||||
window.$events.emit('editor-markdown-change', content);
|
||||
|
||||
// Set body content
|
||||
this.displayDoc.body.className = 'page-content';
|
||||
this.displayDoc.body.innerHTML = html;
|
||||
|
||||
// Copy styles from page head and set custom styles for editor
|
||||
this.loadStylesIntoDisplay();
|
||||
}
|
||||
|
||||
loadStylesIntoDisplay() {
|
||||
if (this.displayStylesLoaded) return;
|
||||
this.displayDoc.documentElement.classList.add('markdown-editor-display');
|
||||
// Set display to be dark mode if parent is
|
||||
|
||||
if (document.documentElement.classList.contains('dark-mode')) {
|
||||
this.displayDoc.documentElement.style.backgroundColor = '#222';
|
||||
this.displayDoc.documentElement.classList.add('dark-mode');
|
||||
}
|
||||
|
||||
this.displayDoc.head.innerHTML = '';
|
||||
const styles = document.head.querySelectorAll('style,link[rel=stylesheet]');
|
||||
for (let style of styles) {
|
||||
const copy = style.cloneNode(true);
|
||||
this.displayDoc.head.appendChild(copy);
|
||||
}
|
||||
|
||||
this.displayStylesLoaded = true;
|
||||
this.display.innerHTML = html;
|
||||
}
|
||||
|
||||
onMarkdownScroll(lineCount) {
|
||||
const elems = this.displayDoc.body.children;
|
||||
const elems = this.display.children;
|
||||
if (elems.length <= lineCount) return;
|
||||
|
||||
const topElem = (lineCount === -1) ? elems[elems.length-1] : elems[lineCount];
|
||||
@@ -317,7 +281,7 @@ class MarkdownEditor {
|
||||
let cursor = cm.getCursor();
|
||||
let lineContent = cm.getLine(cursor.line);
|
||||
let lineLen = lineContent.length;
|
||||
let newLineContent = lineContent;
|
||||
let newLineContent;
|
||||
|
||||
if (lineContent.indexOf(start) === 0 && lineContent.slice(-end.length) === end) {
|
||||
newLineContent = lineContent.slice(start.length, lineContent.length - end.length);
|
||||
@@ -333,9 +297,9 @@ class MarkdownEditor {
|
||||
let selection = cm.getSelection();
|
||||
if (selection === '') return wrapLine(start, end);
|
||||
|
||||
let newSelection = selection;
|
||||
let newSelection;
|
||||
let frontDiff = 0;
|
||||
let endDiff = 0;
|
||||
let endDiff;
|
||||
|
||||
if (selection.indexOf(start) === 0 && selection.slice(-end.length) === end) {
|
||||
newSelection = selection.slice(start.length, selection.length - end.length);
|
||||
@@ -445,10 +409,10 @@ class MarkdownEditor {
|
||||
|
||||
DrawIO.show(url,() => {
|
||||
return Promise.resolve('');
|
||||
}, (pngData) => {
|
||||
}, (drawingData) => {
|
||||
|
||||
const data = {
|
||||
image: pngData,
|
||||
image: drawingData,
|
||||
uploaded_to: Number(this.pageId),
|
||||
};
|
||||
|
||||
@@ -462,7 +426,7 @@ class MarkdownEditor {
|
||||
}
|
||||
|
||||
insertDrawing(image, originalCursor) {
|
||||
const newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`;
|
||||
const newText = DrawIO.buildDrawingContentHtml(image);
|
||||
this.cm.focus();
|
||||
this.cm.replaceSelection(newText);
|
||||
this.cm.setCursor(originalCursor.line, originalCursor.ch + newText.length);
|
||||
@@ -480,21 +444,22 @@ class MarkdownEditor {
|
||||
|
||||
DrawIO.show(drawioUrl, () => {
|
||||
return DrawIO.load(drawingId);
|
||||
}, (pngData) => {
|
||||
}, (drawingData) => {
|
||||
|
||||
let data = {
|
||||
image: pngData,
|
||||
image: drawingData,
|
||||
uploaded_to: Number(this.pageId),
|
||||
};
|
||||
|
||||
window.$http.post("/images/drawio", data).then(resp => {
|
||||
let newText = `<div drawio-diagram="${resp.data.id}"><img src="${resp.data.url}"></div>`;
|
||||
let newContent = this.cm.getValue().split('\n').map(line => {
|
||||
if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) {
|
||||
return newText;
|
||||
}
|
||||
return line;
|
||||
const image = resp.data;
|
||||
const newText = DrawIO.buildDrawingContentHtml(image);
|
||||
|
||||
const newContent = this.cm.getValue().split('\n').map(line => {
|
||||
const isDrawing = line.includes(`drawio-diagram="${drawingId}"`);
|
||||
return isDrawing ? newText : line;
|
||||
}).join('\n');
|
||||
|
||||
this.cm.setValue(newContent);
|
||||
this.cm.setCursor(cursorPos);
|
||||
this.cm.focus();
|
||||
|
||||
@@ -24,6 +24,8 @@ class PageEditor {
|
||||
this.draftDisplayIcon = this.$refs.draftDisplayIcon;
|
||||
this.changelogInput = this.$refs.changelogInput;
|
||||
this.changelogDisplay = this.$refs.changelogDisplay;
|
||||
this.changeEditorButtons = this.$manyRefs.changeEditor || [];
|
||||
this.switchDialogContainer = this.$refs.switchDialog;
|
||||
|
||||
// Translations
|
||||
this.draftText = this.$opts.draftText;
|
||||
@@ -72,6 +74,9 @@ class PageEditor {
|
||||
// Draft Controls
|
||||
onSelect(this.saveDraftButton, this.saveDraft.bind(this));
|
||||
onSelect(this.discardDraftButton, this.discardDraft.bind(this));
|
||||
|
||||
// Change editor controls
|
||||
onSelect(this.changeEditorButtons, this.changeEditor.bind(this));
|
||||
}
|
||||
|
||||
setInitialFocus() {
|
||||
@@ -113,17 +118,21 @@ class PageEditor {
|
||||
data.markdown = this.editorMarkdown;
|
||||
}
|
||||
|
||||
let didSave = false;
|
||||
try {
|
||||
const resp = await window.$http.put(`/ajax/page/${this.pageId}/save-draft`, data);
|
||||
if (!this.isNewDraft) {
|
||||
this.toggleDiscardDraftVisibility(true);
|
||||
}
|
||||
|
||||
this.draftNotifyChange(`${resp.data.message} ${Dates.utcTimeStampToLocalTime(resp.data.timestamp)}`);
|
||||
this.autoSave.last = Date.now();
|
||||
if (resp.data.warning && !this.shownWarningsCache.has(resp.data.warning)) {
|
||||
window.$events.emit('warning', resp.data.warning);
|
||||
this.shownWarningsCache.add(resp.data.warning);
|
||||
}
|
||||
|
||||
didSave = true;
|
||||
} catch (err) {
|
||||
// Save the editor content in LocalStorage as a last resort, just in case.
|
||||
try {
|
||||
@@ -134,6 +143,7 @@ class PageEditor {
|
||||
window.$events.emit('error', this.autosaveFailText);
|
||||
}
|
||||
|
||||
return didSave;
|
||||
}
|
||||
|
||||
draftNotifyChange(text) {
|
||||
@@ -185,6 +195,18 @@ class PageEditor {
|
||||
this.discardDraftWrap.classList.toggle('hidden', !show);
|
||||
}
|
||||
|
||||
async changeEditor(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const link = event.target.closest('a').href;
|
||||
const dialog = this.switchDialogContainer.components['confirm-dialog'];
|
||||
const [saved, confirmed] = await Promise.all([this.saveDraft(), dialog.show()]);
|
||||
|
||||
if (saved && confirmed) {
|
||||
window.location = link;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PageEditor;
|
||||
@@ -34,7 +34,7 @@ class Popup {
|
||||
}
|
||||
|
||||
hide(onComplete = null) {
|
||||
fadeOut(this.container, 240, onComplete);
|
||||
fadeOut(this.container, 120, onComplete);
|
||||
if (this.onkeyup) {
|
||||
window.removeEventListener('keyup', this.onkeyup);
|
||||
this.onkeyup = null;
|
||||
@@ -45,7 +45,7 @@ class Popup {
|
||||
}
|
||||
|
||||
show(onComplete = null, onHide = null) {
|
||||
fadeIn(this.container, 240, onComplete);
|
||||
fadeIn(this.container, 120, onComplete);
|
||||
|
||||
this.onkeyup = (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
|
||||
@@ -43,6 +43,8 @@ function drawReceive(event) {
|
||||
drawEventSave(message);
|
||||
} else if (message.event === 'export') {
|
||||
drawEventExport(message);
|
||||
} else if (message.event === 'configure') {
|
||||
drawEventConfigure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +55,7 @@ function drawEventExport(message) {
|
||||
}
|
||||
|
||||
function drawEventSave(message) {
|
||||
drawPostMessage({action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing'});
|
||||
drawPostMessage({action: 'export', format: 'xmlsvg', xml: message.xml, spin: 'Updating drawing'});
|
||||
}
|
||||
|
||||
function drawEventInit() {
|
||||
@@ -63,6 +65,12 @@ function drawEventInit() {
|
||||
});
|
||||
}
|
||||
|
||||
function drawEventConfigure() {
|
||||
const config = {};
|
||||
window.$events.emitPublic(iFrame, 'editor-drawio::configure', {config});
|
||||
drawPostMessage({action: 'configure', config});
|
||||
}
|
||||
|
||||
function drawEventClose() {
|
||||
window.removeEventListener('message', drawReceive);
|
||||
if (iFrame) document.body.removeChild(iFrame);
|
||||
@@ -88,7 +96,21 @@ async function upload(imageData, pageUploadedToId) {
|
||||
*/
|
||||
async function load(drawingId) {
|
||||
const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`));
|
||||
return `data:image/png;base64,${resp.data.content}`;
|
||||
return resp.data.content;
|
||||
}
|
||||
|
||||
export default {show, close, upload, load};
|
||||
|
||||
function buildDrawingContentHtml(drawing) {
|
||||
const isSvg = drawing.url.split('.').pop().toLowerCase() === 'svg';
|
||||
const image = `<img src="${drawing.url}">`;
|
||||
const embed = `<embed src="${drawing.url}" type="image/svg+xml">`;
|
||||
return `<div drawio-diagram="${drawing.id}">${isSvg ? embed : image}</div>`
|
||||
}
|
||||
|
||||
function buildDrawingContentNode(drawing) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = buildDrawingContentHtml(drawing);
|
||||
return div.children[0];
|
||||
}
|
||||
|
||||
export default {show, close, upload, load, buildDrawingContentHtml, buildDrawingContentNode};
|
||||
@@ -2,6 +2,7 @@ import {register as registerShortcuts} from "./shortcuts";
|
||||
import {listen as listenForCommonEvents} from "./common-events";
|
||||
import {scrollToQueryString} from "./scrolling";
|
||||
import {listenForDragAndPaste} from "./drop-paste-handling";
|
||||
import {getPrimaryToolbar, registerAdditionalToolbars} from "./toolbars";
|
||||
|
||||
import {getPlugin as getCodeeditorPlugin} from "./plugin-codeeditor";
|
||||
import {getPlugin as getDrawioPlugin} from "./plugin-drawio";
|
||||
@@ -9,6 +10,7 @@ import {getPlugin as getCustomhrPlugin} from "./plugins-customhr";
|
||||
import {getPlugin as getImagemanagerPlugin} from "./plugins-imagemanager";
|
||||
import {getPlugin as getAboutPlugin} from "./plugins-about";
|
||||
import {getPlugin as getDetailsPlugin} from "./plugins-details";
|
||||
import {getPlugin as getTasklistPlugin} from "./plugins-tasklist";
|
||||
|
||||
const style_formats = [
|
||||
{title: "Large Header", format: "h2", preview: 'color: blue;'},
|
||||
@@ -58,48 +60,6 @@ function file_picker_callback(callback, value, meta) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WysiwygConfigOptions} options
|
||||
* @return {{toolbar: string, groupButtons: Object<string, Object>}}
|
||||
*/
|
||||
function buildToolbar(options) {
|
||||
const textDirPlugins = options.textDirection === 'rtl' ? 'ltr rtl' : '';
|
||||
|
||||
const groupButtons = {
|
||||
formatoverflow: {
|
||||
icon: 'more-drawer',
|
||||
tooltip: 'More',
|
||||
items: 'strikethrough superscript subscript inlinecode removeformat'
|
||||
},
|
||||
listoverflow: {
|
||||
icon: 'more-drawer',
|
||||
tooltip: 'More',
|
||||
items: 'outdent indent'
|
||||
},
|
||||
insertoverflow: {
|
||||
icon: 'more-drawer',
|
||||
tooltip: 'More',
|
||||
items: 'hr codeeditor drawio media details'
|
||||
}
|
||||
};
|
||||
|
||||
const toolbar = [
|
||||
'undo redo',
|
||||
'styleselect',
|
||||
'bold italic underline forecolor backcolor formatoverflow',
|
||||
'alignleft aligncenter alignright alignjustify',
|
||||
'bullist numlist listoverflow',
|
||||
textDirPlugins,
|
||||
'link table imagemanager-insert insertoverflow',
|
||||
'code about fullscreen'
|
||||
];
|
||||
|
||||
return {
|
||||
toolbar: toolbar.filter(row => Boolean(row)).join(' | '),
|
||||
groupButtons,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WysiwygConfigOptions} options
|
||||
* @return {string}
|
||||
@@ -122,6 +82,7 @@ function gatherPlugins(options) {
|
||||
"imagemanager",
|
||||
"about",
|
||||
"details",
|
||||
"tasklist",
|
||||
options.textDirection === 'rtl' ? 'directionality' : '',
|
||||
];
|
||||
|
||||
@@ -130,6 +91,7 @@ function gatherPlugins(options) {
|
||||
window.tinymce.PluginManager.add('imagemanager', getImagemanagerPlugin(options));
|
||||
window.tinymce.PluginManager.add('about', getAboutPlugin(options));
|
||||
window.tinymce.PluginManager.add('details', getDetailsPlugin(options));
|
||||
window.tinymce.PluginManager.add('tasklist', getTasklistPlugin(options));
|
||||
|
||||
if (options.drawioUrl) {
|
||||
window.tinymce.PluginManager.add('drawio', getDrawioPlugin(options));
|
||||
@@ -152,6 +114,23 @@ function fetchCustomHeadContent() {
|
||||
return headContentLines.slice(startLineIndex + 1, endLineIndex).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a serializer filter for <br> tags to ensure they're not rendered
|
||||
* within code blocks and that we use newlines there instead.
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
function setupBrFilter(editor) {
|
||||
editor.serializer.addNodeFilter('br', function(nodes) {
|
||||
for (const node of nodes) {
|
||||
if (node.parent && node.parent.name === 'code') {
|
||||
const newline = new tinymce.html.Node.create('#text');
|
||||
newline.value = '\n';
|
||||
node.replace(newline);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WysiwygConfigOptions} options
|
||||
* @return {function(Editor)}
|
||||
@@ -169,6 +148,10 @@ function getSetupCallback(options) {
|
||||
window.editor = editor;
|
||||
});
|
||||
|
||||
editor.on('PreInit', () => {
|
||||
setupBrFilter(editor);
|
||||
});
|
||||
|
||||
function editorChange() {
|
||||
const content = editor.getContent();
|
||||
if (options.darkMode) {
|
||||
@@ -218,8 +201,6 @@ export function build(options) {
|
||||
|
||||
// Set language
|
||||
window.tinymce.addI18n(options.language, options.translationMap);
|
||||
// Build toolbar content
|
||||
const {toolbar, groupButtons: toolBarGroupButtons} = buildToolbar(options);
|
||||
|
||||
// BookStack Version
|
||||
const version = document.querySelector('script[src*="/dist/app.js"]').getAttribute('src').split('?version=')[1];
|
||||
@@ -247,7 +228,7 @@ export function build(options) {
|
||||
statusbar: false,
|
||||
menubar: false,
|
||||
paste_data_images: false,
|
||||
extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*]',
|
||||
extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram],details[*],summary[*],div[*],li[class|checked]',
|
||||
automatic_uploads: false,
|
||||
custom_elements: 'doc-root,code-block',
|
||||
valid_children: [
|
||||
@@ -261,7 +242,7 @@ export function build(options) {
|
||||
plugins: gatherPlugins(options),
|
||||
imagetools_toolbar: 'imageoptions',
|
||||
contextmenu: false,
|
||||
toolbar: toolbar,
|
||||
toolbar: getPrimaryToolbar(options),
|
||||
content_style: getContentStyle(options),
|
||||
style_formats,
|
||||
style_formats_merge: false,
|
||||
@@ -281,9 +262,7 @@ export function build(options) {
|
||||
head.innerHTML += fetchCustomHeadContent();
|
||||
},
|
||||
setup(editor) {
|
||||
for (const [key, config] of Object.entries(toolBarGroupButtons)) {
|
||||
editor.ui.registry.addGroupToolbarButton(key, config);
|
||||
}
|
||||
registerAdditionalToolbars(editor, options);
|
||||
getSetupCallback(options)(editor);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -97,11 +97,18 @@ function defineCodeBlockCustomElement(editor) {
|
||||
}
|
||||
|
||||
this.cleanChildContent();
|
||||
const content = this.getContent();
|
||||
const lines = content.split('\n').length;
|
||||
const height = (lines * 19.2) + 18 + 24;
|
||||
this.style.height = `${height}px`;
|
||||
|
||||
const container = this.shadowRoot.querySelector('.CodeMirrorContainer');
|
||||
const renderCodeMirror = (Code) => {
|
||||
this.cm = Code.wysiwygView(container, this.getContent(), this.getLanguage());
|
||||
this.cm = Code.wysiwygView(container, content, this.getLanguage());
|
||||
Code.updateLayout(this.cm);
|
||||
setTimeout(() => {
|
||||
this.style.height = null;
|
||||
}, 1);
|
||||
};
|
||||
|
||||
window.importVersioned('code').then((Code) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import DrawIO from "../services/drawio";
|
||||
import {build} from "./config";
|
||||
|
||||
let pageEditor = null;
|
||||
let currentNode = null;
|
||||
@@ -15,15 +16,14 @@ function isDrawing(node) {
|
||||
function showDrawingManager(mceEditor, selectedNode = null) {
|
||||
pageEditor = mceEditor;
|
||||
currentNode = selectedNode;
|
||||
|
||||
// Show image manager
|
||||
window.ImageManager.show(function (image) {
|
||||
if (selectedNode) {
|
||||
let imgElem = selectedNode.querySelector('img');
|
||||
pageEditor.dom.setAttrib(imgElem, 'src', image.url);
|
||||
pageEditor.dom.setAttrib(selectedNode, 'drawio-diagram', image.id);
|
||||
pageEditor.dom.replace(buildDrawingNode(image), selectedNode);
|
||||
} else {
|
||||
let imgHTML = `<div drawio-diagram="${image.id}" contenteditable="false"><img src="${image.url}"></div>`;
|
||||
pageEditor.insertContent(imgHTML);
|
||||
const drawingHtml = DrawIO.buildDrawingContentHtml(image);
|
||||
pageEditor.insertContent(drawingHtml);
|
||||
}
|
||||
}, 'drawio');
|
||||
}
|
||||
@@ -34,7 +34,14 @@ function showDrawingEditor(mceEditor, selectedNode = null) {
|
||||
DrawIO.show(options.drawioUrl, drawingInit, updateContent);
|
||||
}
|
||||
|
||||
async function updateContent(pngData) {
|
||||
function buildDrawingNode(drawing) {
|
||||
const drawingEl = DrawIO.buildDrawingContentNode(drawing);
|
||||
drawingEl.setAttribute('contenteditable', 'false');
|
||||
drawingEl.setAttribute('data-ephox-embed-iri', 'true');
|
||||
return drawingEl;
|
||||
}
|
||||
|
||||
async function updateContent(drawingData) {
|
||||
const id = "image-" + Math.random().toString(16).slice(2);
|
||||
const loadingImage = window.baseUrl('/loading.gif');
|
||||
|
||||
@@ -50,11 +57,9 @@ async function updateContent(pngData) {
|
||||
// Handle updating an existing image
|
||||
if (currentNode) {
|
||||
DrawIO.close();
|
||||
let imgElem = currentNode.querySelector('img');
|
||||
try {
|
||||
const img = await DrawIO.upload(pngData, options.pageId);
|
||||
pageEditor.dom.setAttrib(imgElem, 'src', img.url);
|
||||
pageEditor.dom.setAttrib(currentNode, 'drawio-diagram', img.id);
|
||||
const img = await DrawIO.upload(drawingData, options.pageId);
|
||||
pageEditor.dom.replace(buildDrawingNode(img), currentNode);
|
||||
} catch (err) {
|
||||
handleUploadError(err);
|
||||
}
|
||||
@@ -62,12 +67,11 @@ async function updateContent(pngData) {
|
||||
}
|
||||
|
||||
setTimeout(async () => {
|
||||
pageEditor.insertContent(`<div drawio-diagram contenteditable="false"><img src="${loadingImage}" id="${id}"></div>`);
|
||||
pageEditor.insertContent(`<div drawio-diagram contenteditable="false"><img src="${loadingImage}" alt="Loading" id="${id}"></div>`);
|
||||
DrawIO.close();
|
||||
try {
|
||||
const img = await DrawIO.upload(pngData, options.pageId);
|
||||
pageEditor.dom.setAttrib(id, 'src', img.url);
|
||||
pageEditor.dom.get(id).parentNode.setAttribute('drawio-diagram', img.id);
|
||||
const img = await DrawIO.upload(drawingData, options.pageId);
|
||||
pageEditor.dom.replace(buildDrawingNode(img), pageEditor.dom.get(id).parentNode);
|
||||
} catch (err) {
|
||||
pageEditor.dom.remove(id);
|
||||
handleUploadError(err);
|
||||
@@ -86,7 +90,6 @@ function drawingInit() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {WysiwygConfigOptions} providedOptions
|
||||
* @return {function(Editor, string)}
|
||||
*/
|
||||
@@ -130,14 +133,28 @@ export function getPlugin(providedOptions) {
|
||||
showDrawingEditor(editor, selectedNode);
|
||||
});
|
||||
|
||||
editor.on('SetContent', function () {
|
||||
const drawings = editor.$('body > div[drawio-diagram]');
|
||||
if (!drawings.length) return;
|
||||
editor.on('PreInit', () => {
|
||||
editor.parser.addNodeFilter('div', function(nodes) {
|
||||
for (const node of nodes) {
|
||||
if (node.attr('drawio-diagram')) {
|
||||
// Set content editable to be false to prevent direct editing of child content.
|
||||
node.attr('contenteditable', 'false');
|
||||
// Set this attribute to prevent drawing contents being parsed as media embeds
|
||||
// to avoid contents being replaced with placeholder images.
|
||||
// TinyMCE embed plugin sources looks for this attribute in its logic.
|
||||
node.attr('data-ephox-embed-iri', 'true');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
editor.undoManager.transact(function () {
|
||||
drawings.each((index, elem) => {
|
||||
elem.setAttribute('contenteditable', 'false');
|
||||
});
|
||||
editor.serializer.addNodeFilter('div', function(nodes) {
|
||||
for (const node of nodes) {
|
||||
// Clean up content attributes
|
||||
if (node.attr('drawio-diagram')) {
|
||||
node.attr('contenteditable', null);
|
||||
node.attr('data-ephox-embed-iri', null);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
171
resources/js/wysiwyg/plugins-tasklist.js
Normal file
171
resources/js/wysiwyg/plugins-tasklist.js
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* @param {Editor} editor
|
||||
* @param {String} url
|
||||
*/
|
||||
function register(editor, url) {
|
||||
|
||||
// Tasklist UI buttons
|
||||
editor.ui.registry.addIcon('tasklist', '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M22,8c0-0.55-0.45-1-1-1h-7c-0.55,0-1,0.45-1,1s0.45,1,1,1h7C21.55,9,22,8.55,22,8z M13,16c0,0.55,0.45,1,1,1h7 c0.55,0,1-0.45,1-1c0-0.55-0.45-1-1-1h-7C13.45,15,13,15.45,13,16z M10.47,4.63c0.39,0.39,0.39,1.02,0,1.41l-4.23,4.25 c-0.39,0.39-1.02,0.39-1.42,0L2.7,8.16c-0.39-0.39-0.39-1.02,0-1.41c0.39-0.39,1.02-0.39,1.41,0l1.42,1.42l3.54-3.54 C9.45,4.25,10.09,4.25,10.47,4.63z M10.48,12.64c0.39,0.39,0.39,1.02,0,1.41l-4.23,4.25c-0.39,0.39-1.02,0.39-1.42,0L2.7,16.16 c-0.39-0.39-0.39-1.02,0-1.41s1.02-0.39,1.41,0l1.42,1.42l3.54-3.54C9.45,12.25,10.09,12.25,10.48,12.64L10.48,12.64z"/></svg>');
|
||||
editor.ui.registry.addToggleButton('tasklist', {
|
||||
tooltip: 'Task list',
|
||||
icon: 'tasklist',
|
||||
active: false,
|
||||
onAction(api) {
|
||||
if (api.isActive()) {
|
||||
editor.execCommand('RemoveList');
|
||||
} else {
|
||||
editor.execCommand('InsertUnorderedList', null, {
|
||||
'list-item-attributes': {
|
||||
class: 'task-list-item',
|
||||
},
|
||||
'list-style-type': 'tasklist',
|
||||
});
|
||||
}
|
||||
},
|
||||
onSetup(api) {
|
||||
editor.on('NodeChange', event => {
|
||||
const parentListEl = event.parents.find(el => el.nodeName === 'LI');
|
||||
const inList = parentListEl && parentListEl.classList.contains('task-list-item');
|
||||
api.setActive(inList);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Tweak existing bullet list button active state to not be active
|
||||
// when we're in a task list.
|
||||
const existingBullListButton = editor.ui.registry.getAll().buttons.bullist;
|
||||
existingBullListButton.onSetup = function(api) {
|
||||
editor.on('NodeChange', event => {
|
||||
const parentList = event.parents.find(el => el.nodeName === 'LI');
|
||||
const inTaskList = parentList && parentList.classList.contains('task-list-item');
|
||||
const inUlList = parentList && parentList.parentNode.nodeName === 'UL';
|
||||
api.setActive(inUlList && !inTaskList);
|
||||
});
|
||||
};
|
||||
existingBullListButton.onAction = function() {
|
||||
// Cheeky hack to prevent list toggle action treating tasklists as normal
|
||||
// unordered lists which would unwrap the list on toggle from tasklist to bullet list.
|
||||
// Instead we quickly jump through an ordered list first if we're within a tasklist.
|
||||
if (elementWithinTaskList(editor.selection.getNode())) {
|
||||
editor.execCommand('InsertOrderedList', null, {
|
||||
'list-item-attributes': {class: null}
|
||||
});
|
||||
}
|
||||
|
||||
editor.execCommand('InsertUnorderedList', null, {
|
||||
'list-item-attributes': {class: null}
|
||||
});
|
||||
};
|
||||
// Tweak existing number list to not allow classes on child items
|
||||
const existingNumListButton = editor.ui.registry.getAll().buttons.numlist;
|
||||
existingNumListButton.onAction = function() {
|
||||
editor.execCommand('InsertOrderedList', null, {
|
||||
'list-item-attributes': {class: null}
|
||||
});
|
||||
};
|
||||
|
||||
// Setup filters on pre-init
|
||||
editor.on('PreInit', () => {
|
||||
editor.parser.addNodeFilter('li', function(nodes) {
|
||||
for (const node of nodes) {
|
||||
if (node.attributes.map.class === 'task-list-item') {
|
||||
parseTaskListNode(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
editor.serializer.addNodeFilter('li', function(nodes) {
|
||||
for (const node of nodes) {
|
||||
if (node.attributes.map.class === 'task-list-item') {
|
||||
serializeTaskListNode(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle checkbox click in editor
|
||||
editor.on('click', function(event) {
|
||||
const clickedEl = event.target;
|
||||
if (clickedEl.nodeName === 'LI' && clickedEl.classList.contains('task-list-item')) {
|
||||
handleTaskListItemClick(event, clickedEl, editor);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Element} element
|
||||
* @return {boolean}
|
||||
*/
|
||||
function elementWithinTaskList(element) {
|
||||
const listEl = element.closest('li');
|
||||
return listEl && listEl.parentNode.nodeName === 'UL' && listEl.classList.contains('task-list-item');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} event
|
||||
* @param {Element} clickedEl
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
function handleTaskListItemClick(event, clickedEl, editor) {
|
||||
const bounds = clickedEl.getBoundingClientRect();
|
||||
const withinBounds = event.clientX <= bounds.right
|
||||
&& event.clientX >= bounds.left
|
||||
&& event.clientY >= bounds.top
|
||||
&& event.clientY <= bounds.bottom;
|
||||
|
||||
// Outside of the task list item bounds mean we're probably clicking the pseudo-element.
|
||||
if (!withinBounds) {
|
||||
editor.undoManager.transact(() => {
|
||||
if (clickedEl.hasAttribute('checked')) {
|
||||
clickedEl.removeAttribute('checked');
|
||||
} else {
|
||||
clickedEl.setAttribute('checked', 'checked');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AstNode} node
|
||||
*/
|
||||
function parseTaskListNode(node) {
|
||||
// Force task list item class
|
||||
node.attr('class', 'task-list-item');
|
||||
|
||||
// Copy checkbox status and remove checkbox within editor
|
||||
for (const child of node.children()) {
|
||||
if (child.name === 'input') {
|
||||
if (child.attr('checked') === 'checked') {
|
||||
node.attr('checked', 'checked');
|
||||
}
|
||||
child.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AstNode} node
|
||||
*/
|
||||
function serializeTaskListNode(node) {
|
||||
// Get checked status and clean it from list node
|
||||
const isChecked = node.attr('checked') === 'checked';
|
||||
node.attr('checked', null);
|
||||
|
||||
const inputAttrs = {type: 'checkbox', disabled: 'disabled'};
|
||||
if (isChecked) {
|
||||
inputAttrs.checked = 'checked';
|
||||
}
|
||||
|
||||
// Create & insert checkbox input element
|
||||
const checkbox = new tinymce.html.Node.create('input', inputAttrs);
|
||||
checkbox.shortEnded = true;
|
||||
node.firstChild ? node.insert(checkbox, node.firstChild, true) : node.append(checkbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WysiwygConfigOptions} options
|
||||
* @return {register}
|
||||
*/
|
||||
export function getPlugin(options) {
|
||||
return register;
|
||||
}
|
||||
@@ -39,4 +39,19 @@ export function register(editor) {
|
||||
|
||||
editor.formatter.apply('callout' + newFormat);
|
||||
});
|
||||
|
||||
// Link selector shortcut
|
||||
editor.shortcuts.add('meta+shift+K', '', function() {
|
||||
window.EntitySelectorPopup.show(function(entity) {
|
||||
|
||||
if (editor.selection.isCollapsed()) {
|
||||
editor.insertContent(editor.dom.createHTML('a', {href: entity.link}, editor.dom.encode(entity.name)));
|
||||
} else {
|
||||
editor.formatter.apply('link', {href: entity.link});
|
||||
}
|
||||
|
||||
editor.selection.collapse(false);
|
||||
editor.focus();
|
||||
})
|
||||
});
|
||||
}
|
||||
64
resources/js/wysiwyg/toolbars.js
Normal file
64
resources/js/wysiwyg/toolbars.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @param {WysiwygConfigOptions} options
|
||||
* @return {String}
|
||||
*/
|
||||
export function getPrimaryToolbar(options) {
|
||||
const textDirPlugins = options.textDirection === 'rtl' ? 'ltr rtl' : '';
|
||||
|
||||
const toolbar = [
|
||||
'undo redo',
|
||||
'styleselect',
|
||||
'bold italic underline forecolor backcolor formatoverflow',
|
||||
'alignleft aligncenter alignright alignjustify',
|
||||
'bullist numlist listoverflow',
|
||||
textDirPlugins,
|
||||
'link table imagemanager-insert insertoverflow',
|
||||
'code about fullscreen'
|
||||
];
|
||||
|
||||
return toolbar.filter(row => Boolean(row)).join(' | ');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
function registerPrimaryToolbarGroups(editor) {
|
||||
editor.ui.registry.addGroupToolbarButton('formatoverflow', {
|
||||
icon: 'more-drawer',
|
||||
tooltip: 'More',
|
||||
items: 'strikethrough superscript subscript inlinecode removeformat'
|
||||
});
|
||||
editor.ui.registry.addGroupToolbarButton('listoverflow', {
|
||||
icon: 'more-drawer',
|
||||
tooltip: 'More',
|
||||
items: 'tasklist outdent indent'
|
||||
});
|
||||
editor.ui.registry.addGroupToolbarButton('insertoverflow', {
|
||||
icon: 'more-drawer',
|
||||
tooltip: 'More',
|
||||
items: 'hr codeeditor drawio media details'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
function registerLinkContextToolbar(editor) {
|
||||
editor.ui.registry.addContextToolbar('linkcontexttoolbar', {
|
||||
predicate(node) {
|
||||
return node.closest('a') !== null;
|
||||
},
|
||||
position: 'node',
|
||||
scope: 'node',
|
||||
items: 'link unlink openlink'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Editor} editor
|
||||
* @param {WysiwygConfigOptions} options
|
||||
*/
|
||||
export function registerAdditionalToolbars(editor, options) {
|
||||
registerPrimaryToolbarGroups(editor);
|
||||
registerLinkContextToolbar(editor);
|
||||
}
|
||||
@@ -7,61 +7,61 @@ return [
|
||||
|
||||
// Pages
|
||||
'page_create' => 'تم إنشاء صفحة',
|
||||
'page_create_notification' => 'Page successfully created',
|
||||
'page_create_notification' => 'تم إنشاء الصفحة بنجاح',
|
||||
'page_update' => 'تم تحديث الصفحة',
|
||||
'page_update_notification' => 'Page successfully updated',
|
||||
'page_update_notification' => 'تم تحديث الصفحة بنجاح',
|
||||
'page_delete' => 'تم حذف الصفحة',
|
||||
'page_delete_notification' => 'Page successfully deleted',
|
||||
'page_delete_notification' => 'تم حذف الصفحة بنجاح',
|
||||
'page_restore' => 'تمت استعادة الصفحة',
|
||||
'page_restore_notification' => 'Page successfully restored',
|
||||
'page_restore_notification' => 'تمت استعادة الصفحة بنجاح',
|
||||
'page_move' => 'تم نقل الصفحة',
|
||||
|
||||
// Chapters
|
||||
'chapter_create' => 'تم إنشاء فصل',
|
||||
'chapter_create_notification' => 'Chapter successfully created',
|
||||
'chapter_create_notification' => 'تم إنشاء الفصل بنجاح',
|
||||
'chapter_update' => 'تم تحديث الفصل',
|
||||
'chapter_update_notification' => 'Chapter successfully updated',
|
||||
'chapter_update_notification' => 'تم تحديث الفصل بنجاح',
|
||||
'chapter_delete' => 'تم حذف الفصل',
|
||||
'chapter_delete_notification' => 'Chapter successfully deleted',
|
||||
'chapter_delete_notification' => 'تم حذف الفصل بنجاح',
|
||||
'chapter_move' => 'تم نقل الفصل',
|
||||
|
||||
// Books
|
||||
'book_create' => 'تم إنشاء كتاب',
|
||||
'book_create_notification' => 'Book successfully created',
|
||||
'book_create_notification' => 'تم إنشاء الكتاب بنجاح',
|
||||
'book_update' => 'تم تحديث الكتاب',
|
||||
'book_update_notification' => 'Book successfully updated',
|
||||
'book_update_notification' => 'تم تحديث الكتاب بنجاح',
|
||||
'book_delete' => 'تم حذف الكتاب',
|
||||
'book_delete_notification' => 'Book successfully deleted',
|
||||
'book_delete_notification' => 'تم حذف الكتاب بنجاح',
|
||||
'book_sort' => 'تم سرد الكتاب',
|
||||
'book_sort_notification' => 'Book successfully re-sorted',
|
||||
'book_sort_notification' => 'تم إعادة فرز الكتاب بنجاح',
|
||||
|
||||
// Bookshelves
|
||||
'bookshelf_create' => 'created bookshelf',
|
||||
'bookshelf_create_notification' => 'Bookshelf successfully created',
|
||||
'bookshelf_create' => 'تم إنشاء رف كتب',
|
||||
'bookshelf_create_notification' => 'تم إنشاء الرف بنجاح',
|
||||
'bookshelf_update' => 'تم تحديث الرف',
|
||||
'bookshelf_update_notification' => 'Bookshelf successfully updated',
|
||||
'bookshelf_update_notification' => 'تم تحديث الرف بنجاح',
|
||||
'bookshelf_delete' => 'تم تحديث الرف',
|
||||
'bookshelf_delete_notification' => 'Bookshelf successfully deleted',
|
||||
'bookshelf_delete_notification' => 'تم حذف الرف بنجاح',
|
||||
|
||||
// Favourites
|
||||
'favourite_add_notification' => '":name" has been added to your favourites',
|
||||
'favourite_remove_notification' => '":name" has been removed from your favourites',
|
||||
'favourite_add_notification' => 'تم إضافة ":name" إلى المفضلة لديك',
|
||||
'favourite_remove_notification' => 'تم إزالة ":name" من المفضلة لديك',
|
||||
|
||||
// MFA
|
||||
'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
|
||||
'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
|
||||
'mfa_setup_method_notification' => 'تم تكوين طريقة متعددة العوامل بنجاح',
|
||||
'mfa_remove_method_notification' => 'تمت إزالة طريقة متعددة العوامل بنجاح',
|
||||
|
||||
// Webhooks
|
||||
'webhook_create' => 'created webhook',
|
||||
'webhook_create_notification' => 'Webhook successfully created',
|
||||
'webhook_update' => 'updated webhook',
|
||||
'webhook_update_notification' => 'Webhook successfully updated',
|
||||
'webhook_delete' => 'deleted webhook',
|
||||
'webhook_delete_notification' => 'Webhook successfully deleted',
|
||||
'webhook_create' => 'تم إنشاء webhook',
|
||||
'webhook_create_notification' => 'تم إنشاء Webhook بنجاح',
|
||||
'webhook_update' => 'تم تحديث webhook',
|
||||
'webhook_update_notification' => 'تم تحديث Webhook بنجاح',
|
||||
'webhook_delete' => 'حذف webhook',
|
||||
'webhook_delete_notification' => 'تم حذف Webhook بنجاح',
|
||||
|
||||
// Users
|
||||
'user_update_notification' => 'User successfully updated',
|
||||
'user_delete_notification' => 'User successfully removed',
|
||||
'user_update_notification' => 'تم تحديث المستخدم بنجاح',
|
||||
'user_delete_notification' => 'تم إزالة المستخدم بنجاح',
|
||||
|
||||
// Other
|
||||
'commented_on' => 'تم التعليق',
|
||||
|
||||
@@ -24,6 +24,7 @@ return [
|
||||
'width' => 'العرض',
|
||||
'height' => 'الارتفاع',
|
||||
'More' => 'المزيد',
|
||||
'select' => 'Select...',
|
||||
|
||||
// Toolbar
|
||||
'formats' => 'التنسيقات',
|
||||
@@ -52,9 +53,10 @@ return [
|
||||
'align_left' => 'محاذاة لليسار',
|
||||
'align_center' => 'محاذاة بالمنتصف',
|
||||
'align_right' => 'مُحاذاة لليمين',
|
||||
'align_justify' => 'ضبط المحاذاة',
|
||||
'align_justify' => 'Justify',
|
||||
'list_bullet' => 'قائمة نقاط',
|
||||
'list_numbered' => 'قائمة مرقمة',
|
||||
'list_task' => 'Task list',
|
||||
'indent_increase' => 'زيادة البادئة',
|
||||
'indent_decrease' => 'إنقاص البادئة',
|
||||
'table' => 'جدول',
|
||||
@@ -91,7 +93,10 @@ return [
|
||||
'cell_properties_title' => 'Cell Properties',
|
||||
'cell_type' => 'Cell type',
|
||||
'cell_type_cell' => 'Cell',
|
||||
'cell_scope' => 'Scope',
|
||||
'cell_type_header' => 'Header cell',
|
||||
'merge_cells' => 'Merge cells',
|
||||
'split_cell' => 'Split cell',
|
||||
'table_row_group' => 'Row Group',
|
||||
'table_column_group' => 'Column Group',
|
||||
'horizontal_align' => 'Horizontal align',
|
||||
@@ -119,6 +124,16 @@ return [
|
||||
'caption' => 'الوصف',
|
||||
'show_caption' => 'إظهار الوصف',
|
||||
'constrain' => 'Constrain proportions',
|
||||
'cell_border_solid' => 'Solid',
|
||||
'cell_border_dotted' => 'Dotted',
|
||||
'cell_border_dashed' => 'Dashed',
|
||||
'cell_border_double' => 'Double',
|
||||
'cell_border_groove' => 'Groove',
|
||||
'cell_border_ridge' => 'Ridge',
|
||||
'cell_border_inset' => 'Inset',
|
||||
'cell_border_outset' => 'Outset',
|
||||
'cell_border_none' => 'None',
|
||||
'cell_border_hidden' => 'Hidden',
|
||||
|
||||
// Images, links, details/summary & embed
|
||||
'source' => 'Source',
|
||||
@@ -139,12 +154,14 @@ return [
|
||||
'toggle_label' => 'Toggle label',
|
||||
|
||||
// About view
|
||||
'about' => 'About the editor',
|
||||
'about_title' => 'About the WYSIWYG Editor',
|
||||
'editor_license' => 'Editor License & Copyright',
|
||||
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.',
|
||||
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
|
||||
'save_continue' => 'Save Page & Continue',
|
||||
'callouts_cycle' => '(Keep pressing to toggle through types)',
|
||||
'link_selector' => 'Link to content',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcut' => 'Shortcut',
|
||||
'shortcuts_intro' => 'The following shortcuts are available in the editor:',
|
||||
|
||||
@@ -196,9 +196,19 @@ return [
|
||||
'pages_edit_draft_save_at' => 'تم خفظ المسودة في ',
|
||||
'pages_edit_delete_draft' => 'حذف المسودة',
|
||||
'pages_edit_discard_draft' => 'التخلص من المسودة',
|
||||
'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
|
||||
'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
|
||||
'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
|
||||
'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
|
||||
'pages_edit_set_changelog' => 'تثبيت سجل التعديل',
|
||||
'pages_edit_enter_changelog_desc' => 'ضع وصف مختصر للتعديلات التي تمت',
|
||||
'pages_edit_enter_changelog' => 'أدخل سجل التعديل',
|
||||
'pages_editor_switch_title' => 'Switch Editor',
|
||||
'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
|
||||
'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
|
||||
'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
|
||||
'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
|
||||
'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
|
||||
'pages_save' => 'حفظ الصفحة',
|
||||
'pages_title' => 'عنوان الصفحة',
|
||||
'pages_name' => 'اسم الصفحة',
|
||||
@@ -225,6 +235,7 @@ return [
|
||||
'pages_revisions_number' => '#',
|
||||
'pages_revisions_numbered' => 'مراجعة #:id',
|
||||
'pages_revisions_numbered_changes' => 'مراجعة #: رقم تعريفي التغييرات',
|
||||
'pages_revisions_editor' => 'Editor Type',
|
||||
'pages_revisions_changelog' => 'سجل التعديل',
|
||||
'pages_revisions_changes' => 'التعديلات',
|
||||
'pages_revisions_current' => 'النسخة الحالية',
|
||||
|
||||
@@ -10,6 +10,8 @@ return [
|
||||
'settings' => 'الإعدادات',
|
||||
'settings_save' => 'حفظ الإعدادات',
|
||||
'settings_save_success' => 'تم حفظ الإعدادات',
|
||||
'system_version' => 'System Version',
|
||||
'categories' => 'Categories',
|
||||
|
||||
// App Settings
|
||||
'app_customization' => 'تخصيص',
|
||||
@@ -25,8 +27,8 @@ return [
|
||||
'app_secure_images' => 'تفعيل حماية أكبر لرفع الصور؟',
|
||||
'app_secure_images_toggle' => 'لمزيد من الحماية',
|
||||
'app_secure_images_desc' => 'لتحسين أداء النظام, ستكون جميع الصور متاحة للعامة. هذا الخيار يضيف سلسلة من الحروف والأرقام العشوائية صعبة التخمين إلى رابط الصورة. الرجاء التأكد من تعطيل فهرسة المسارات لمنع الوصول السهل.',
|
||||
'app_editor' => 'محرر الصفحة',
|
||||
'app_editor_desc' => 'الرجاء اختيار محرر النص الذي سيستخدم من قبل جميع المستخدمين لتحرير الصفحات.',
|
||||
'app_default_editor' => 'Default Page Editor',
|
||||
'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
|
||||
'app_custom_html' => 'Custom HTML head content',
|
||||
'app_custom_html_desc' => 'سيتم إدراج أي محتوى مضاف هنا في الجزء السفلي من قسم <head> من كل صفحة. هذا أمر مفيد لتجاوز الأنماط أو إضافة رمز التحليل.',
|
||||
'app_custom_html_disabled_notice' => 'تم تعطيل محتوى HTML الرئيسي المخصص في صفحة الإعدادات هذه لضمان عكس أي تغييرات متتالية.',
|
||||
@@ -150,6 +152,7 @@ return [
|
||||
'role_access_api' => 'الوصول إلى واجهة برمجة تطبيقات النظام API',
|
||||
'role_manage_settings' => 'إدارة إعدادات التطبيق',
|
||||
'role_export_content' => 'Export content',
|
||||
'role_editor_change' => 'Change page editor',
|
||||
'role_asset' => 'أذونات الأصول',
|
||||
'roles_system_warning' => 'اعلم أن الوصول إلى أي من الأذونات الثلاثة المذكورة أعلاه يمكن أن يسمح للمستخدم بتغيير امتيازاته الخاصة أو امتيازات الآخرين في النظام. قم بتعيين الأدوار مع هذه الأذونات فقط للمستخدمين الموثوق بهم.',
|
||||
'role_asset_desc' => 'تتحكم هذه الأذونات في الوصول الافتراضي إلى الأصول داخل النظام. ستتجاوز الأذونات الخاصة بالكتب والفصول والصفحات هذه الأذونات.',
|
||||
@@ -275,6 +278,8 @@ return [
|
||||
'es' => 'Español',
|
||||
'es_AR' => 'Español Argentina',
|
||||
'et' => 'Eesti keel',
|
||||
'eu' => 'Euskara',
|
||||
'fa' => 'فارسی',
|
||||
'fr' => 'Français',
|
||||
'he' => 'עברית',
|
||||
'hr' => 'Hrvatski',
|
||||
|
||||
@@ -7,63 +7,63 @@ return [
|
||||
|
||||
// Pages
|
||||
'page_create' => 'създадена страница',
|
||||
'page_create_notification' => 'Page successfully created',
|
||||
'page_create_notification' => 'Страницата е създадена успешно',
|
||||
'page_update' => 'обновена страница',
|
||||
'page_update_notification' => 'Page successfully updated',
|
||||
'page_update_notification' => 'Страницата е обновена успешно',
|
||||
'page_delete' => 'изтрита страница',
|
||||
'page_delete_notification' => 'Page successfully deleted',
|
||||
'page_delete_notification' => 'Страницата е изтрита успешно',
|
||||
'page_restore' => 'възстановена страница',
|
||||
'page_restore_notification' => 'Page successfully restored',
|
||||
'page_restore_notification' => 'Страницата е възстановена успешно',
|
||||
'page_move' => 'преместена страница',
|
||||
|
||||
// Chapters
|
||||
'chapter_create' => 'създадена страница',
|
||||
'chapter_create_notification' => 'Chapter successfully created',
|
||||
'chapter_create_notification' => 'Главата е добавена успешно',
|
||||
'chapter_update' => 'обновена глава',
|
||||
'chapter_update_notification' => 'Chapter successfully updated',
|
||||
'chapter_update_notification' => 'Главата е обновена успешно',
|
||||
'chapter_delete' => 'изтрита глава',
|
||||
'chapter_delete_notification' => 'Chapter successfully deleted',
|
||||
'chapter_delete_notification' => 'Главата е изтрита успешно',
|
||||
'chapter_move' => 'преместена глава',
|
||||
|
||||
// Books
|
||||
'book_create' => 'създадена книга',
|
||||
'book_create_notification' => 'Book successfully created',
|
||||
'book_create_notification' => 'Книгата е създадена успешно',
|
||||
'book_update' => 'обновена книга',
|
||||
'book_update_notification' => 'Book successfully updated',
|
||||
'book_update_notification' => 'Книгата е обновена успешно',
|
||||
'book_delete' => 'изтрита книга',
|
||||
'book_delete_notification' => 'Book successfully deleted',
|
||||
'book_delete_notification' => 'Книгата е изтрита успешно',
|
||||
'book_sort' => 'сортирана книга',
|
||||
'book_sort_notification' => 'Book successfully re-sorted',
|
||||
'book_sort_notification' => 'Книгата е преподредена успешно',
|
||||
|
||||
// Bookshelves
|
||||
'bookshelf_create' => 'created bookshelf',
|
||||
'bookshelf_create_notification' => 'Bookshelf successfully created',
|
||||
'bookshelf_create' => 'създаден рафт',
|
||||
'bookshelf_create_notification' => 'Рафтът е създаден успешно',
|
||||
'bookshelf_update' => 'обновен рафт',
|
||||
'bookshelf_update_notification' => 'Bookshelf successfully updated',
|
||||
'bookshelf_update_notification' => 'Рафтът е обновен успешно',
|
||||
'bookshelf_delete' => 'изтрит рафт',
|
||||
'bookshelf_delete_notification' => 'Bookshelf successfully deleted',
|
||||
'bookshelf_delete_notification' => 'Рафтът е изтрит успешно',
|
||||
|
||||
// Favourites
|
||||
'favourite_add_notification' => '":name" has been added to your favourites',
|
||||
'favourite_remove_notification' => '":name" has been removed from your favourites',
|
||||
'favourite_add_notification' => '":name" е добавен към любими успешно',
|
||||
'favourite_remove_notification' => '":name" е премахнат от любими успешно',
|
||||
|
||||
// MFA
|
||||
'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
|
||||
'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
|
||||
'mfa_setup_method_notification' => 'Многофакторният метод е конфигуриран успешно',
|
||||
'mfa_remove_method_notification' => 'Многофакторният метод е премахнат успешно',
|
||||
|
||||
// Webhooks
|
||||
'webhook_create' => 'created webhook',
|
||||
'webhook_create_notification' => 'Webhook successfully created',
|
||||
'webhook_update' => 'updated webhook',
|
||||
'webhook_update_notification' => 'Webhook successfully updated',
|
||||
'webhook_delete' => 'deleted webhook',
|
||||
'webhook_delete_notification' => 'Webhook successfully deleted',
|
||||
'webhook_create' => 'създадена уебкука',
|
||||
'webhook_create_notification' => 'Уебкуката е създадена успешно',
|
||||
'webhook_update' => 'обновена уебкука',
|
||||
'webhook_update_notification' => 'Уебкуката е обновена успешно',
|
||||
'webhook_delete' => 'изтрита уебкука',
|
||||
'webhook_delete_notification' => 'Уебкуката е изтрита успешно',
|
||||
|
||||
// Users
|
||||
'user_update_notification' => 'User successfully updated',
|
||||
'user_delete_notification' => 'User successfully removed',
|
||||
'user_update_notification' => 'Потребителят е обновен успешно',
|
||||
'user_delete_notification' => 'Потребителят е премахнат успешно',
|
||||
|
||||
// Other
|
||||
'commented_on' => 'коментирано на',
|
||||
'permissions_update' => 'updated permissions',
|
||||
'permissions_update' => 'обновени права',
|
||||
];
|
||||
|
||||
@@ -17,23 +17,23 @@ return [
|
||||
'logout' => 'Изход',
|
||||
|
||||
'name' => 'Име',
|
||||
'username' => 'Потребител',
|
||||
'username' => 'Потребителско име',
|
||||
'email' => 'Имейл',
|
||||
'password' => 'Парола',
|
||||
'password_confirm' => 'Потвърди паролата',
|
||||
'password_hint' => 'Must be at least 8 characters',
|
||||
'password_hint' => 'Трябва да бъде поне 8 символа',
|
||||
'forgot_password' => 'Забравена парола?',
|
||||
'remember_me' => 'Запомни ме',
|
||||
'ldap_email_hint' => 'Моля въведете емейл, който да използвате за дадения акаунт.',
|
||||
'ldap_email_hint' => 'Моля въведете емейл, който да използвате за дадения профил.',
|
||||
'create_account' => 'Създай Акаунт',
|
||||
'already_have_account' => 'Вече имате акаунт?',
|
||||
'already_have_account' => 'Вече имате профил?',
|
||||
'dont_have_account' => 'Нямате акаунт?',
|
||||
'social_login' => 'Влизане по друг начин',
|
||||
'social_registration' => 'Регистрация по друг начин',
|
||||
'social_registration_text' => 'Регистрация и влизане използвайки друг начин.',
|
||||
'social_registration_text' => 'Регистрация и вписване чрез друга услуга.',
|
||||
|
||||
'register_thanks' => 'Благодарим Ви за регистрацията!',
|
||||
'register_confirm' => 'Моля проверете своя емейл и натиснете върху бутона за потвърждение, за да влезете в :appName.',
|
||||
'register_confirm' => 'Моля, провери своя имейл адрес и натисни бутона за потвърждение, за да достъпиш :appName.',
|
||||
'registrations_disabled' => 'Регистрациите към момента са забранени',
|
||||
'registration_email_domain_invalid' => 'Този емейл домейн към момента няма достъп до приложението',
|
||||
'register_success' => 'Благодарим Ви за регистрацията! В момента сте регистриран и сте вписани в приложението.',
|
||||
@@ -41,11 +41,11 @@ return [
|
||||
// Password Reset
|
||||
'reset_password' => 'Нулиране на паролата',
|
||||
'reset_password_send_instructions' => 'Въведете емейла си и ще ви бъде изпратен емейл с линк за нулиране на паролата.',
|
||||
'reset_password_send_button' => 'Изпращане на линк за нулиране',
|
||||
'reset_password_send_button' => 'Изпращане на линк за възстановяване',
|
||||
'reset_password_sent' => 'Линк за нулиране на паролата ще Ви бъде изпратен на :email, ако емейлът Ви бъде открит в системата.',
|
||||
'reset_password_success' => 'Паролата Ви е променена успешно.',
|
||||
'email_reset_subject' => 'Възстановете паролата си за :appName',
|
||||
'email_reset_text' => 'Вие получихте този емейл, защото поискахте вашата парола да бъде занулена.',
|
||||
'email_reset_subject' => 'Възстанови паролата си за :appName',
|
||||
'email_reset_text' => 'Вие получихте този имейл, защото поискахте Вашата парола да бъде възстановена.',
|
||||
'email_reset_not_requested' => 'Ако Вие не сте поискали зануляването на паролата, няма нужда от други действия.',
|
||||
|
||||
// Email Confirmation
|
||||
@@ -54,7 +54,7 @@ return [
|
||||
'email_confirm_text' => 'Моля, потвърдете вашия имейл адрес, като следвате връзката по-долу:',
|
||||
'email_confirm_action' => 'Потвърдете имейл',
|
||||
'email_confirm_send_error' => 'Нужно ви е потвърждение чрез емейл, но системата не успя да го изпрати. Моля свържете се с администратора, за да проверите дали вашият емейл адрес е конфигуриран правилно.',
|
||||
'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.',
|
||||
'email_confirm_success' => 'Имейлът ти е потвърден! Вече би трябвало да можеш да се впишеш с този имейл адрес.',
|
||||
'email_confirm_resent' => 'Беше изпратен имейл с потвърждение, Моля, проверете кутията си.',
|
||||
|
||||
'email_not_confirmed' => 'Имейл адресът не е потвърден',
|
||||
@@ -71,40 +71,40 @@ return [
|
||||
'user_invite_page_welcome' => 'Добре дошли в :appName!',
|
||||
'user_invite_page_text' => 'За да финализирате вашият акаунт и да получите достъп трябва да определите парола, която да бъде използвана за следващия влизания в :appName.',
|
||||
'user_invite_page_confirm_button' => 'Потвърди паролата',
|
||||
'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!',
|
||||
'user_invite_success_login' => 'Паролата е настроена, вече можеш да се впишеш с новата парола, за да достъпиш :appName!',
|
||||
|
||||
// Multi-factor Authentication
|
||||
'mfa_setup' => 'Setup Multi-Factor Authentication',
|
||||
'mfa_setup_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.',
|
||||
'mfa_setup_configured' => 'Already configured',
|
||||
'mfa_setup_reconfigure' => 'Reconfigure',
|
||||
'mfa_setup_remove_confirmation' => 'Are you sure you want to remove this multi-factor authentication method?',
|
||||
'mfa_setup_action' => 'Setup',
|
||||
'mfa_backup_codes_usage_limit_warning' => 'You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.',
|
||||
'mfa_option_totp_title' => 'Mobile App',
|
||||
'mfa_option_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.',
|
||||
'mfa_option_backup_codes_title' => 'Backup Codes',
|
||||
'mfa_option_backup_codes_desc' => 'Securely store a set of one-time-use backup codes which you can enter to verify your identity.',
|
||||
'mfa_gen_confirm_and_enable' => 'Confirm and Enable',
|
||||
'mfa_gen_backup_codes_title' => 'Backup Codes Setup',
|
||||
'mfa_gen_backup_codes_desc' => 'Store the below list of codes in a safe place. When accessing the system you\'ll be able to use one of the codes as a second authentication mechanism.',
|
||||
'mfa_gen_backup_codes_download' => 'Download Codes',
|
||||
'mfa_gen_backup_codes_usage_warning' => 'Each code can only be used once',
|
||||
'mfa_gen_totp_title' => 'Mobile App Setup',
|
||||
'mfa_gen_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.',
|
||||
'mfa_gen_totp_scan' => 'Scan the QR code below using your preferred authentication app to get started.',
|
||||
'mfa_gen_totp_verify_setup' => 'Verify Setup',
|
||||
'mfa_gen_totp_verify_setup_desc' => 'Verify that all is working by entering a code, generated within your authentication app, in the input box below:',
|
||||
'mfa_gen_totp_provide_code_here' => 'Provide your app generated code here',
|
||||
'mfa_verify_access' => 'Verify Access',
|
||||
'mfa_verify_access_desc' => 'Your user account requires you to confirm your identity via an additional level of verification before you\'re granted access. Verify using one of your configured methods to continue.',
|
||||
'mfa_verify_no_methods' => 'No Methods Configured',
|
||||
'mfa_verify_no_methods_desc' => 'No multi-factor authentication methods could be found for your account. You\'ll need to set up at least one method before you gain access.',
|
||||
'mfa_verify_use_totp' => 'Verify using a mobile app',
|
||||
'mfa_verify_use_backup_codes' => 'Verify using a backup code',
|
||||
'mfa_verify_backup_code' => 'Backup Code',
|
||||
'mfa_verify_backup_code_desc' => 'Enter one of your remaining backup codes below:',
|
||||
'mfa_verify_backup_code_enter_here' => 'Enter backup code here',
|
||||
'mfa_verify_totp_desc' => 'Enter the code, generated using your mobile app, below:',
|
||||
'mfa_setup_login_notification' => 'Multi-factor method configured, Please now login again using the configured method.',
|
||||
'mfa_setup' => 'Настрой многофакторно удостоверяване',
|
||||
'mfa_setup_desc' => 'Настрой многофакторно удостверяване като втори слой сигурност на твоя профил.',
|
||||
'mfa_setup_configured' => 'Вече е конфигурирано',
|
||||
'mfa_setup_reconfigure' => 'Преконфигурирай',
|
||||
'mfa_setup_remove_confirmation' => 'Сигурен ли си, че желаеш да премахнеш този метод за многофакторно удостоверяване?',
|
||||
'mfa_setup_action' => 'Настройка',
|
||||
'mfa_backup_codes_usage_limit_warning' => 'Имаш по-малко от 5 останали резервни кода. Генерирай и съхрани нов набор, преди тези да са свършили, за да избегнеш да останеш без достъп до профила си.',
|
||||
'mfa_option_totp_title' => 'Мобилно приложение',
|
||||
'mfa_option_totp_desc' => 'За да използваш многофакторно удостоверяване, ще ти трябва мобилно приложение, което поддържа временни еднократни пароли (TOTP), като например Google Authenticator, Authy или Microsoft Authenticator.',
|
||||
'mfa_option_backup_codes_title' => 'Резервни кодове',
|
||||
'mfa_option_backup_codes_desc' => 'Запази на сигурно място набор от еднократни резервни кодове, с които можеш да устовериш идентичността си.',
|
||||
'mfa_gen_confirm_and_enable' => 'Потвърди и включи',
|
||||
'mfa_gen_backup_codes_title' => 'Настройка на резервни кодове',
|
||||
'mfa_gen_backup_codes_desc' => 'Запази този лист с кодове на сигурно място. Когато достъпваш системата, ще можеш да използваш един от тези кодове като вторичен механизъм за удостоверяване.',
|
||||
'mfa_gen_backup_codes_download' => 'Изтегли кодовете',
|
||||
'mfa_gen_backup_codes_usage_warning' => 'Всеки код може да бъде използван само веднъж',
|
||||
'mfa_gen_totp_title' => 'Настройка на мобилно приложение',
|
||||
'mfa_gen_totp_desc' => 'За да използваш многофакторно удостоверяване, ще ти трябва мобилно приложение, което поддържа временни еднократни пароли (TOTP), като например Google Authenticator, Authy или Microsoft Authenticator.',
|
||||
'mfa_gen_totp_scan' => 'За да започнеш, сканирай QR кода отдолу с предпочитано от теб приложение.',
|
||||
'mfa_gen_totp_verify_setup' => 'Потвърди настройката',
|
||||
'mfa_gen_totp_verify_setup_desc' => 'Потвърди, че всичко работи, като в кутията отдолу въведеш код, генериран от твоето приложение за удостоверяване:',
|
||||
'mfa_gen_totp_provide_code_here' => 'Въведи тук кода, генериран от мобилното ти приложение',
|
||||
'mfa_verify_access' => 'Потвърди достъпа',
|
||||
'mfa_verify_access_desc' => 'Твоят потребителски профил изисква да потвърдиш идентичността си чрез допълнително ниво проверка преди да получиш достъп. Потвърди чрез един от конфигурираните методи, за да продължиш.',
|
||||
'mfa_verify_no_methods' => 'Няма конфигурирани методи',
|
||||
'mfa_verify_no_methods_desc' => 'Няма намерени методи за многофакторно удостоверяване за твоя профил. Ще трябва да настроиш поне един метод, преди да получиш достъп.',
|
||||
'mfa_verify_use_totp' => 'Потвърди чрез мобилно приложение',
|
||||
'mfa_verify_use_backup_codes' => 'Потвърди чрез резервен код',
|
||||
'mfa_verify_backup_code' => 'Резервен код',
|
||||
'mfa_verify_backup_code_desc' => 'Въведи един от останалите ти резервни кодове отдолу:',
|
||||
'mfa_verify_backup_code_enter_here' => 'Въведи резервен код тук',
|
||||
'mfa_verify_totp_desc' => 'Въведи кода, генериран от мобилното ти приложение, отдолу:',
|
||||
'mfa_setup_login_notification' => 'Многофакторният метод е конфигуриран, моля да се впишете отново чрез конфигурирания метод.',
|
||||
];
|
||||
|
||||
@@ -39,14 +39,14 @@ return [
|
||||
'reset' => 'Нулирай',
|
||||
'remove' => 'Премахване',
|
||||
'add' => 'Добави',
|
||||
'configure' => 'Configure',
|
||||
'configure' => 'Конфигурирай',
|
||||
'fullscreen' => 'Пълен екран',
|
||||
'favourite' => 'Favourite',
|
||||
'unfavourite' => 'Unfavourite',
|
||||
'next' => 'Next',
|
||||
'previous' => 'Previous',
|
||||
'filter_active' => 'Active Filter:',
|
||||
'filter_clear' => 'Clear Filter',
|
||||
'favourite' => 'Добави в любими',
|
||||
'unfavourite' => 'Премахни от любими',
|
||||
'next' => 'Следващ',
|
||||
'previous' => 'Предишен',
|
||||
'filter_active' => 'Активен филтър:',
|
||||
'filter_clear' => 'Изчисти филтъра',
|
||||
|
||||
// Sort Options
|
||||
'sort_options' => 'Опции за сортиране',
|
||||
@@ -54,7 +54,7 @@ return [
|
||||
'sort_ascending' => 'Сортирай възходящо',
|
||||
'sort_descending' => 'Низходящо сортиране',
|
||||
'sort_name' => 'Име',
|
||||
'sort_default' => 'Default',
|
||||
'sort_default' => 'По подразбиране',
|
||||
'sort_created_at' => 'Дата на създаване',
|
||||
'sort_updated_at' => 'Дата на обновяване',
|
||||
|
||||
@@ -63,7 +63,7 @@ return [
|
||||
'no_activity' => 'Няма активност за показване',
|
||||
'no_items' => 'Няма налични артикули',
|
||||
'back_to_top' => 'Върнете се в началото',
|
||||
'skip_to_main_content' => 'Skip to main content',
|
||||
'skip_to_main_content' => 'Прескочи към основното съдържание',
|
||||
'toggle_details' => 'Активирай детайли',
|
||||
'toggle_thumbnails' => 'Активирай миниатюри',
|
||||
'details' => 'Подробности',
|
||||
@@ -71,14 +71,14 @@ return [
|
||||
'list_view' => 'Изглед списък',
|
||||
'default' => 'Основен',
|
||||
'breadcrumb' => 'Трасиране',
|
||||
'status' => 'Status',
|
||||
'status_active' => 'Active',
|
||||
'status_inactive' => 'Inactive',
|
||||
'never' => 'Never',
|
||||
'none' => 'None',
|
||||
'status' => 'Статус',
|
||||
'status_active' => 'Активен',
|
||||
'status_inactive' => 'Неактивен',
|
||||
'never' => 'Никога',
|
||||
'none' => 'Няма',
|
||||
|
||||
// Header
|
||||
'header_menu_expand' => 'Expand Header Menu',
|
||||
'header_menu_expand' => 'Разшири заглавното меню',
|
||||
'profile_menu' => 'Профил меню',
|
||||
'view_profile' => 'Разглеждане на профил',
|
||||
'edit_profile' => 'Редактиране на профила',
|
||||
@@ -87,9 +87,9 @@ return [
|
||||
|
||||
// Layout tabs
|
||||
'tab_info' => 'Информация',
|
||||
'tab_info_label' => 'Tab: Show Secondary Information',
|
||||
'tab_info_label' => 'Таб: Покажи вторична информация',
|
||||
'tab_content' => 'Съдържание',
|
||||
'tab_content_label' => 'Tab: Show Primary Content',
|
||||
'tab_content_label' => 'Таб: Покажи първично съдържание',
|
||||
|
||||
// Email Content
|
||||
'email_action_help' => 'Ако имате проблеми с бутона ":actionText" по-горе, копирайте и поставете URL адреса по-долу в уеб браузъра си:',
|
||||
|
||||
@@ -29,6 +29,6 @@ return [
|
||||
'code_editor' => 'Редактиране на кода',
|
||||
'code_language' => 'Език на кода',
|
||||
'code_content' => 'Съдържание на кода',
|
||||
'code_session_history' => 'Session History',
|
||||
'code_session_history' => 'История на сесиите',
|
||||
'code_save' => 'Запази кода',
|
||||
];
|
||||
|
||||
@@ -7,148 +7,165 @@
|
||||
*/
|
||||
return [
|
||||
// General editor terms
|
||||
'general' => 'General',
|
||||
'advanced' => 'Advanced',
|
||||
'none' => 'None',
|
||||
'cancel' => 'Cancel',
|
||||
'save' => 'Save',
|
||||
'close' => 'Close',
|
||||
'undo' => 'Undo',
|
||||
'redo' => 'Redo',
|
||||
'left' => 'Left',
|
||||
'center' => 'Center',
|
||||
'right' => 'Right',
|
||||
'top' => 'Top',
|
||||
'middle' => 'Middle',
|
||||
'bottom' => 'Bottom',
|
||||
'width' => 'Width',
|
||||
'height' => 'Height',
|
||||
'More' => 'More',
|
||||
'general' => 'Общи',
|
||||
'advanced' => 'Разширени',
|
||||
'none' => 'Няма',
|
||||
'cancel' => 'Откажи',
|
||||
'save' => 'Запази',
|
||||
'close' => 'Затвори',
|
||||
'undo' => 'Отмени',
|
||||
'redo' => 'Преправи',
|
||||
'left' => 'Вляво',
|
||||
'center' => 'По средата',
|
||||
'right' => 'Вдясно',
|
||||
'top' => 'Отгоре',
|
||||
'middle' => 'Среда',
|
||||
'bottom' => 'Отдолу',
|
||||
'width' => 'Широчина',
|
||||
'height' => 'Височина',
|
||||
'More' => 'Още',
|
||||
'select' => 'Select...',
|
||||
|
||||
// Toolbar
|
||||
'formats' => 'Formats',
|
||||
'header_large' => 'Large Header',
|
||||
'header_medium' => 'Medium Header',
|
||||
'header_small' => 'Small Header',
|
||||
'header_tiny' => 'Tiny Header',
|
||||
'paragraph' => 'Paragraph',
|
||||
'blockquote' => 'Blockquote',
|
||||
'inline_code' => 'Inline code',
|
||||
'callouts' => 'Callouts',
|
||||
'callout_information' => 'Information',
|
||||
'callout_success' => 'Success',
|
||||
'callout_warning' => 'Warning',
|
||||
'callout_danger' => 'Danger',
|
||||
'bold' => 'Bold',
|
||||
'italic' => 'Italic',
|
||||
'underline' => 'Underline',
|
||||
'strikethrough' => 'Strikethrough',
|
||||
'superscript' => 'Superscript',
|
||||
'subscript' => 'Subscript',
|
||||
'text_color' => 'Text color',
|
||||
'custom_color' => 'Custom color',
|
||||
'remove_color' => 'Remove color',
|
||||
'background_color' => 'Background color',
|
||||
'align_left' => 'Align left',
|
||||
'align_center' => 'Align center',
|
||||
'align_right' => 'Align right',
|
||||
'align_justify' => 'Align justify',
|
||||
'list_bullet' => 'Bullet list',
|
||||
'list_numbered' => 'Numbered list',
|
||||
'indent_increase' => 'Increase indent',
|
||||
'indent_decrease' => 'Decrease indent',
|
||||
'table' => 'Table',
|
||||
'insert_image' => 'Insert image',
|
||||
'insert_image_title' => 'Insert/Edit Image',
|
||||
'insert_link' => 'Insert/edit link',
|
||||
'insert_link_title' => 'Insert/Edit Link',
|
||||
'insert_horizontal_line' => 'Insert horizontal line',
|
||||
'insert_code_block' => 'Insert code block',
|
||||
'insert_drawing' => 'Insert/edit drawing',
|
||||
'drawing_manager' => 'Drawing manager',
|
||||
'insert_media' => 'Insert/edit media',
|
||||
'insert_media_title' => 'Insert/Edit Media',
|
||||
'clear_formatting' => 'Clear formatting',
|
||||
'source_code' => 'Source code',
|
||||
'source_code_title' => 'Source Code',
|
||||
'fullscreen' => 'Fullscreen',
|
||||
'image_options' => 'Image options',
|
||||
'formats' => 'Формати',
|
||||
'header_large' => 'Голяма заглавка',
|
||||
'header_medium' => 'Средна заглавка',
|
||||
'header_small' => 'Малка заглавка',
|
||||
'header_tiny' => 'Миниатюрна заглавка',
|
||||
'paragraph' => 'Параграф',
|
||||
'blockquote' => 'Цитат',
|
||||
'inline_code' => 'Вложен код',
|
||||
'callouts' => 'Призиви',
|
||||
'callout_information' => 'Информация',
|
||||
'callout_success' => 'Успех',
|
||||
'callout_warning' => 'Предупреждение',
|
||||
'callout_danger' => 'Опасност',
|
||||
'bold' => 'Удебелено',
|
||||
'italic' => 'Наклонен',
|
||||
'underline' => 'Подчертан',
|
||||
'strikethrough' => 'Зачертан',
|
||||
'superscript' => 'Горен индекс',
|
||||
'subscript' => 'Долен индекс',
|
||||
'text_color' => 'Цвят на текста',
|
||||
'custom_color' => 'Цвят по избор',
|
||||
'remove_color' => 'Премахване на цвят',
|
||||
'background_color' => 'Фонов цвят',
|
||||
'align_left' => 'Приравни вляво',
|
||||
'align_center' => 'Приравни в центъра',
|
||||
'align_right' => 'Приравни вдясно',
|
||||
'align_justify' => 'Justify',
|
||||
'list_bullet' => 'Списък',
|
||||
'list_numbered' => 'Номериран списък',
|
||||
'list_task' => 'Task list',
|
||||
'indent_increase' => 'Увеличаване на отстъпа',
|
||||
'indent_decrease' => 'Намаляване на отстъпа',
|
||||
'table' => 'Таблица',
|
||||
'insert_image' => 'Вмъкни изображение',
|
||||
'insert_image_title' => 'Вмъкни/редактирай изображение',
|
||||
'insert_link' => 'Вмъкни/редактирай връзка',
|
||||
'insert_link_title' => 'Вмъкни/редактирай връзка',
|
||||
'insert_horizontal_line' => 'Вмъкни хоризонтална линия',
|
||||
'insert_code_block' => 'Въведи код',
|
||||
'insert_drawing' => 'Вмъкни/редактирай рисунка',
|
||||
'drawing_manager' => 'Управление на рисунките',
|
||||
'insert_media' => 'Вмъкни/редактирай мултимедия',
|
||||
'insert_media_title' => 'Вмъкни/редактирай мултимедия',
|
||||
'clear_formatting' => 'Изчисти форматирането',
|
||||
'source_code' => 'Изходен код',
|
||||
'source_code_title' => 'Изходен код',
|
||||
'fullscreen' => 'Цял екран',
|
||||
'image_options' => 'Настройки на изображението',
|
||||
|
||||
// Tables
|
||||
'table_properties' => 'Table properties',
|
||||
'table_properties_title' => 'Table Properties',
|
||||
'delete_table' => 'Delete table',
|
||||
'insert_row_before' => 'Insert row before',
|
||||
'insert_row_after' => 'Insert row after',
|
||||
'delete_row' => 'Delete row',
|
||||
'insert_column_before' => 'Insert column before',
|
||||
'insert_column_after' => 'Insert column after',
|
||||
'delete_column' => 'Delete column',
|
||||
'table_cell' => 'Cell',
|
||||
'table_row' => 'Row',
|
||||
'table_column' => 'Column',
|
||||
'cell_properties' => 'Cell properties',
|
||||
'cell_properties_title' => 'Cell Properties',
|
||||
'cell_type' => 'Cell type',
|
||||
'cell_type_cell' => 'Cell',
|
||||
'cell_type_header' => 'Header cell',
|
||||
'table_row_group' => 'Row Group',
|
||||
'table_column_group' => 'Column Group',
|
||||
'horizontal_align' => 'Horizontal align',
|
||||
'vertical_align' => 'Vertical align',
|
||||
'border_width' => 'Border width',
|
||||
'border_style' => 'Border style',
|
||||
'border_color' => 'Border color',
|
||||
'row_properties' => 'Row properties',
|
||||
'row_properties_title' => 'Row Properties',
|
||||
'cut_row' => 'Cut row',
|
||||
'copy_row' => 'Copy row',
|
||||
'paste_row_before' => 'Paste row before',
|
||||
'paste_row_after' => 'Paste row after',
|
||||
'row_type' => 'Row type',
|
||||
'row_type_header' => 'Header',
|
||||
'row_type_body' => 'Body',
|
||||
'row_type_footer' => 'Footer',
|
||||
'alignment' => 'Alignment',
|
||||
'cut_column' => 'Cut column',
|
||||
'copy_column' => 'Copy column',
|
||||
'paste_column_before' => 'Paste column before',
|
||||
'paste_column_after' => 'Paste column after',
|
||||
'cell_padding' => 'Cell padding',
|
||||
'cell_spacing' => 'Cell spacing',
|
||||
'caption' => 'Caption',
|
||||
'show_caption' => 'Show caption',
|
||||
'constrain' => 'Constrain proportions',
|
||||
'table_properties' => 'Настройки на таблицата',
|
||||
'table_properties_title' => 'Настройки на таблицата',
|
||||
'delete_table' => 'Изтрий таблицата',
|
||||
'insert_row_before' => 'Вмъкни реда преди',
|
||||
'insert_row_after' => 'Вмъкни реда след',
|
||||
'delete_row' => 'Изтрий реда',
|
||||
'insert_column_before' => 'Вмъкни колоната преди',
|
||||
'insert_column_after' => 'Вмъкни колоната след',
|
||||
'delete_column' => 'Изтрий колоната',
|
||||
'table_cell' => 'Клетка',
|
||||
'table_row' => 'Ред',
|
||||
'table_column' => 'Колона',
|
||||
'cell_properties' => 'Настройки на клетката',
|
||||
'cell_properties_title' => 'Настройки на клетката',
|
||||
'cell_type' => 'Тип на клетката',
|
||||
'cell_type_cell' => 'Клетка',
|
||||
'cell_scope' => 'Scope',
|
||||
'cell_type_header' => 'Заглавна клетка',
|
||||
'merge_cells' => 'Merge cells',
|
||||
'split_cell' => 'Split cell',
|
||||
'table_row_group' => 'Група от редове',
|
||||
'table_column_group' => 'Група от колони',
|
||||
'horizontal_align' => 'Хоризонтално разположение',
|
||||
'vertical_align' => 'Вертикално разположение',
|
||||
'border_width' => 'Дължината на рамката',
|
||||
'border_style' => 'Стил на рамката',
|
||||
'border_color' => 'Цвят на рамката',
|
||||
'row_properties' => 'Свойства на реда',
|
||||
'row_properties_title' => 'Свойства на реда',
|
||||
'cut_row' => 'Изрежи реда',
|
||||
'copy_row' => 'Копирай реда',
|
||||
'paste_row_before' => 'Постави реда преди',
|
||||
'paste_row_after' => 'Постави реда след',
|
||||
'row_type' => 'Тип на реда',
|
||||
'row_type_header' => 'Заглавка',
|
||||
'row_type_body' => 'Тяло',
|
||||
'row_type_footer' => 'Долна част',
|
||||
'alignment' => 'Разположение',
|
||||
'cut_column' => 'Изрежи колоната',
|
||||
'copy_column' => 'Копирай колоната',
|
||||
'paste_column_before' => 'Постави колоната преди',
|
||||
'paste_column_after' => 'Постави колоната след',
|
||||
'cell_padding' => 'Отстояние на клетката',
|
||||
'cell_spacing' => 'Отстояние на клетката',
|
||||
'caption' => 'Надпис',
|
||||
'show_caption' => 'Покажи надпис',
|
||||
'constrain' => 'Ограничи пропорциите',
|
||||
'cell_border_solid' => 'Solid',
|
||||
'cell_border_dotted' => 'Dotted',
|
||||
'cell_border_dashed' => 'Dashed',
|
||||
'cell_border_double' => 'Double',
|
||||
'cell_border_groove' => 'Groove',
|
||||
'cell_border_ridge' => 'Ridge',
|
||||
'cell_border_inset' => 'Inset',
|
||||
'cell_border_outset' => 'Outset',
|
||||
'cell_border_none' => 'None',
|
||||
'cell_border_hidden' => 'Hidden',
|
||||
|
||||
// Images, links, details/summary & embed
|
||||
'source' => 'Source',
|
||||
'alt_desc' => 'Alternative description',
|
||||
'embed' => 'Embed',
|
||||
'paste_embed' => 'Paste your embed code below:',
|
||||
'source' => 'Източник',
|
||||
'alt_desc' => 'Алтернативно описание',
|
||||
'embed' => 'Вгради',
|
||||
'paste_embed' => 'Постави кода за вмъкване отдолу:',
|
||||
'url' => 'URL',
|
||||
'text_to_display' => 'Text to display',
|
||||
'title' => 'Title',
|
||||
'open_link' => 'Open link in...',
|
||||
'open_link_current' => 'Current window',
|
||||
'open_link_new' => 'New window',
|
||||
'insert_collapsible' => 'Insert collapsible block',
|
||||
'collapsible_unwrap' => 'Unwrap',
|
||||
'edit_label' => 'Edit label',
|
||||
'toggle_open_closed' => 'Toggle open/closed',
|
||||
'collapsible_edit' => 'Edit collapsible block',
|
||||
'toggle_label' => 'Toggle label',
|
||||
'text_to_display' => 'Текст за показване',
|
||||
'title' => 'Заглавие',
|
||||
'open_link' => 'Отваряне не връзката в...',
|
||||
'open_link_current' => 'Текущ прозорец',
|
||||
'open_link_new' => 'Нов прозорец',
|
||||
'insert_collapsible' => 'Вмъкни сгъваем блок',
|
||||
'collapsible_unwrap' => 'Разгъни',
|
||||
'edit_label' => 'Редактирай етикета',
|
||||
'toggle_open_closed' => 'Превключи отворено/затворено',
|
||||
'collapsible_edit' => 'Редактирай сгъваем блок',
|
||||
'toggle_label' => 'Превключи надписа',
|
||||
|
||||
// About view
|
||||
'about_title' => 'About the WYSIWYG Editor',
|
||||
'editor_license' => 'Editor License & Copyright',
|
||||
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.',
|
||||
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
|
||||
'save_continue' => 'Save Page & Continue',
|
||||
'callouts_cycle' => '(Keep pressing to toggle through types)',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcut' => 'Shortcut',
|
||||
'shortcuts_intro' => 'The following shortcuts are available in the editor:',
|
||||
'about' => 'About the editor',
|
||||
'about_title' => 'Относно визуалния редактор',
|
||||
'editor_license' => 'Лиценз, авторски и сходни права на редактора',
|
||||
'editor_tiny_license' => 'Този редактор е създаден с :tinyLink, който е предоставен с лиценз LGPL v2.1.',
|
||||
'editor_tiny_license_link' => 'Авторското и сходните му права, както и лицензът на TinyMCE, могат да бъдат намерени тук.',
|
||||
'save_continue' => 'Запази страницата и продължи',
|
||||
'callouts_cycle' => '(Продължавай да натискаш, за да превключваш типовете)',
|
||||
'link_selector' => 'Свържи със съдържанието',
|
||||
'shortcuts' => 'Преки пътища',
|
||||
'shortcut' => 'Пряк път',
|
||||
'shortcuts_intro' => 'Следните клавишни комбинации са налични за редактора:',
|
||||
'windows_linux' => '(Windows/Linux)',
|
||||
'mac' => '(Mac)',
|
||||
'description' => 'Description',
|
||||
'description' => 'Описание',
|
||||
];
|
||||
|
||||
@@ -27,7 +27,7 @@ return [
|
||||
'images' => 'Изображения',
|
||||
'my_recent_drafts' => 'Моите скорошни драфтове',
|
||||
'my_recently_viewed' => 'Моите скорошни преглеждания',
|
||||
'my_most_viewed_favourites' => 'My Most Viewed Favourites',
|
||||
'my_most_viewed_favourites' => 'Моите най-преглеждани любими',
|
||||
'my_favourites' => 'Моите фаворити',
|
||||
'no_pages_viewed' => 'Не сте прегледали никакви страници',
|
||||
'no_pages_recently_created' => 'Не са били създавани страници скоро',
|
||||
@@ -36,7 +36,7 @@ return [
|
||||
'export_html' => 'Прикачени уеб файлове',
|
||||
'export_pdf' => 'PDF файл',
|
||||
'export_text' => 'Обикновен текстов файл',
|
||||
'export_md' => 'Markdown File',
|
||||
'export_md' => 'Markdown файл',
|
||||
|
||||
// Permissions and restrictions
|
||||
'permissions' => 'Права',
|
||||
@@ -53,7 +53,7 @@ return [
|
||||
'search_for_term' => 'Търси :term',
|
||||
'search_more' => 'Още резултати',
|
||||
'search_advanced' => 'Подробно търсене',
|
||||
'search_terms' => 'Search Terms',
|
||||
'search_terms' => 'Термини за търсене',
|
||||
'search_content_type' => 'Тип на съдържание',
|
||||
'search_exact_matches' => 'Точни съвпадения',
|
||||
'search_tags' => 'Търсене на тагове',
|
||||
@@ -99,7 +99,7 @@ return [
|
||||
'shelves_permissions' => 'Настройки за достъп до рафта с книги',
|
||||
'shelves_permissions_updated' => 'Настройките за достъп до рафта с книги е обновен',
|
||||
'shelves_permissions_active' => 'Настройките за достъп до рафта с книги е активен',
|
||||
'shelves_permissions_cascade_warning' => 'Permissions on bookshelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.',
|
||||
'shelves_permissions_cascade_warning' => 'Привилегиите на рафтовете не се разпространяват автоматично към съдържаните в тях книги. Това е така, защото една книга може да съществува на няколко различни рафта. Въпреки това, привилегиите могат да бъдат копирани до книгите вътре чрез опцията отдолу.',
|
||||
'shelves_copy_permissions_to_books' => 'Копирай настойките за достъп към книгите',
|
||||
'shelves_copy_permissions' => 'Копирай настройките за достъп',
|
||||
'shelves_copy_permissions_explain' => 'Това ще приложи настоящите настройки за достъп на този рафт с книги за всички книги, съдържащи се в него. Преди да активирате, уверете се, че всички промени в настройките за достъп на този рафт са запазени.',
|
||||
@@ -143,8 +143,8 @@ return [
|
||||
'books_sort_chapters_last' => 'Последна глава',
|
||||
'books_sort_show_other' => 'Покажи други книги',
|
||||
'books_sort_save' => 'Запази новата подредба',
|
||||
'books_copy' => 'Copy Book',
|
||||
'books_copy_success' => 'Book successfully copied',
|
||||
'books_copy' => 'Копирай книгата',
|
||||
'books_copy_success' => 'Книгата е копирана успешно',
|
||||
|
||||
// Chapters
|
||||
'chapter' => 'Глава',
|
||||
@@ -155,7 +155,7 @@ return [
|
||||
'chapters_create' => 'Създай нова глава',
|
||||
'chapters_delete' => 'Изтрий глава',
|
||||
'chapters_delete_named' => 'Изтрий глава :chapterName',
|
||||
'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages that exist within this chapter will also be deleted.',
|
||||
'chapters_delete_explain' => 'Това ще изтрие главата \':chapterName\'. Всички страници в главата също ще бъдат изтрити.',
|
||||
'chapters_delete_confirm' => 'Сигурни ли сте, че искате да изтриете тази глава?',
|
||||
'chapters_edit' => 'Редактирай глава',
|
||||
'chapters_edit_named' => 'Актуализирай глава :chapterName',
|
||||
@@ -163,8 +163,8 @@ return [
|
||||
'chapters_move' => 'Премести глава',
|
||||
'chapters_move_named' => 'Премести глава :chapterName',
|
||||
'chapter_move_success' => 'Главата беше преместена в :bookName',
|
||||
'chapters_copy' => 'Copy Chapter',
|
||||
'chapters_copy_success' => 'Chapter successfully copied',
|
||||
'chapters_copy' => 'Копирай главата',
|
||||
'chapters_copy_success' => 'Главата е копирана успешно',
|
||||
'chapters_permissions' => 'Настойки за достъп на главата',
|
||||
'chapters_empty' => 'Няма създадени страници в тази глава.',
|
||||
'chapters_permissions_active' => 'Настройките за достъп до глава са активни',
|
||||
@@ -196,9 +196,19 @@ return [
|
||||
'pages_edit_draft_save_at' => 'Черновата е запазена в ',
|
||||
'pages_edit_delete_draft' => 'Изтрий чернова',
|
||||
'pages_edit_discard_draft' => 'Отхвърляне на черновата',
|
||||
'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
|
||||
'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
|
||||
'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
|
||||
'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
|
||||
'pages_edit_set_changelog' => 'Задайте регистър на промените',
|
||||
'pages_edit_enter_changelog_desc' => 'Въведете кратко резюме на промените, които сте създали',
|
||||
'pages_edit_enter_changelog' => 'Въведи регистър на промените',
|
||||
'pages_editor_switch_title' => 'Switch Editor',
|
||||
'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
|
||||
'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
|
||||
'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
|
||||
'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
|
||||
'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
|
||||
'pages_save' => 'Запазване на страницата',
|
||||
'pages_title' => 'Заглавие на страницата',
|
||||
'pages_name' => 'Име на страницата',
|
||||
@@ -219,12 +229,13 @@ return [
|
||||
'pages_revisions' => 'Ревизии на страницата',
|
||||
'pages_revisions_named' => 'Ревизии на страницата :pageName',
|
||||
'pages_revision_named' => 'Ревизия на страницата :pageName',
|
||||
'pages_revision_restored_from' => 'Restored from #:id; :summary',
|
||||
'pages_revision_restored_from' => 'Възстановено от #:id; :summary',
|
||||
'pages_revisions_created_by' => 'Създадено от',
|
||||
'pages_revisions_date' => 'Дата на ревизията',
|
||||
'pages_revisions_number' => '№',
|
||||
'pages_revisions_numbered' => 'Ревизия №:id',
|
||||
'pages_revisions_numbered_changes' => 'Ревизия №:id Промени',
|
||||
'pages_revisions_editor' => 'Editor Type',
|
||||
'pages_revisions_changelog' => 'История на промените',
|
||||
'pages_revisions_changes' => 'Промени',
|
||||
'pages_revisions_current' => 'Текуща версия',
|
||||
@@ -238,7 +249,7 @@ return [
|
||||
'pages_initial_name' => 'Нова страница',
|
||||
'pages_editing_draft_notification' => 'В момента редактирате чернова, която беше последно обновена :timeDiff.',
|
||||
'pages_draft_edited_notification' => 'Тази страница беше актуализирана от тогава. Препоръчително е да изтриете настоящата чернова.',
|
||||
'pages_draft_page_changed_since_creation' => 'This page has been updated since this draft was created. It is recommended that you discard this draft or take care not to overwrite any page changes.',
|
||||
'pages_draft_page_changed_since_creation' => 'Страницата е била обновена от създаването на черновата. Препоръчително е да изтриеш черновата или да се погрижиш да не презапишеш промени по страницата.',
|
||||
'pages_draft_edit_active' => [
|
||||
'start_a' => ':count потребителя започнаха да редактират настоящата страница',
|
||||
'start_b' => ':userName в момента редактира тази страница',
|
||||
@@ -262,16 +273,16 @@ return [
|
||||
'tags_explain' => "Добавете няколко тага за да категоризирате по добре вашето съдържание. \n Може да добавите съдържание на таговете за по-подробна организация.",
|
||||
'tags_add' => 'Добави друг таг',
|
||||
'tags_remove' => 'Премахни този таг',
|
||||
'tags_usages' => 'Total tag usages',
|
||||
'tags_assigned_pages' => 'Assigned to Pages',
|
||||
'tags_assigned_chapters' => 'Assigned to Chapters',
|
||||
'tags_assigned_books' => 'Assigned to Books',
|
||||
'tags_assigned_shelves' => 'Assigned to Shelves',
|
||||
'tags_x_unique_values' => ':count unique values',
|
||||
'tags_all_values' => 'All values',
|
||||
'tags_view_tags' => 'View Tags',
|
||||
'tags_view_existing_tags' => 'View existing tags',
|
||||
'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.',
|
||||
'tags_usages' => 'Общо ползвания на таг',
|
||||
'tags_assigned_pages' => 'Присвоен на страници',
|
||||
'tags_assigned_chapters' => 'Присвоен на глави',
|
||||
'tags_assigned_books' => 'Присвоен на книги',
|
||||
'tags_assigned_shelves' => 'Присвоен на рафтове',
|
||||
'tags_x_unique_values' => ':count уникални стойности',
|
||||
'tags_all_values' => 'Всички стойности',
|
||||
'tags_view_tags' => 'Виж тагове',
|
||||
'tags_view_existing_tags' => 'Виж съществуващи тагове',
|
||||
'tags_list_empty_hint' => 'Таговете могат да бъдат прилагани чрез страничната лента в редактора на страници или по време на редактирането на детайлите за книги, глави или рафтове.',
|
||||
'attachments' => 'Прикачени файлове',
|
||||
'attachments_explain' => 'Прикачете файлове или линкове, които да са видими на вашата страница. Същите ще бъдат видими във вашето странично поле.',
|
||||
'attachments_explain_instant_save' => 'Промените тук се запазват веднага.',
|
||||
@@ -288,7 +299,7 @@ return [
|
||||
'attachments_link_url' => 'Линк към файла',
|
||||
'attachments_link_url_hint' => 'Url на сайт или файл',
|
||||
'attach' => 'Прикачване',
|
||||
'attachments_insert_link' => 'Add Attachment Link to Page',
|
||||
'attachments_insert_link' => 'Добави линк на прикачен файл към страница',
|
||||
'attachments_edit_file' => 'Редактирай файл',
|
||||
'attachments_edit_file_name' => 'Име на файл',
|
||||
'attachments_edit_drop_upload' => 'Поставете файл или цъкнете тук за да прикачите и обновите',
|
||||
@@ -338,10 +349,10 @@ return [
|
||||
'revision_cannot_delete_latest' => 'Не може да изтриете последната версия.',
|
||||
|
||||
// Copy view
|
||||
'copy_consider' => 'Please consider the below when copying content.',
|
||||
'copy_consider_permissions' => 'Custom permission settings will not be copied.',
|
||||
'copy_consider_owner' => 'You will become the owner of all copied content.',
|
||||
'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.',
|
||||
'copy_consider_attachments' => 'Page attachments will not be copied.',
|
||||
'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.',
|
||||
'copy_consider' => 'Моля, имай предвид долното при копиране на съдържание.',
|
||||
'copy_consider_permissions' => 'Специфичните настройки на привилегиите няма да бъдат копирани.',
|
||||
'copy_consider_owner' => 'Ти ще станеш собственикът на цялото копирано съдържание.',
|
||||
'copy_consider_images' => 'Файловете на изображенията в страницата няма да бъдат дубликирани и оригиналните изображения ще запазят връзката си със страницата, на която са били качени първоначално.',
|
||||
'copy_consider_attachments' => 'Прикачените към страницата обекти няма да бъдат копирани.',
|
||||
'copy_consider_access' => 'Смяна на местоположението, собственика или привилегиите може да направи това съдържание достъпно за тези, които не са го виждали преди.',
|
||||
];
|
||||
|
||||
@@ -11,10 +11,10 @@ return [
|
||||
// Auth
|
||||
'error_user_exists_different_creds' => 'Потребител с емайл :email вече съществува но с други данни.',
|
||||
'email_already_confirmed' => 'Емейлът вече беше потвърден. Моля опитрайте да влезете.',
|
||||
'email_confirmation_invalid' => 'Този код за достъп не е валиден или вече е бил използван, Моля опитрайте се да се регистрирате отново.',
|
||||
'email_confirmation_invalid' => 'Този код за достъп не е валиден или вече е бил използван, Моля опитай да се регистрираш отново.',
|
||||
'email_confirmation_expired' => 'Кодът за потвърждение изтече, нов емейл за потвърждение беше изпратен.',
|
||||
'email_confirmation_awaiting' => 'Емайл адреса, който използвате трябва да се потвърди',
|
||||
'ldap_fail_anonymous' => 'LDAP протокола прекъсна, използвайки анонимни настройки',
|
||||
'ldap_fail_anonymous' => 'LDAP достъпът е неуспешен с анонимни настройки',
|
||||
'ldap_fail_authed' => 'Опита за достъп чрез LDAP с използваната парола не беше успешен',
|
||||
'ldap_extension_not_installed' => 'LDAP PHP не беше инсталирана',
|
||||
'ldap_cannot_connect' => 'Не може да се свържете с Ldap сървъра, първоначалната връзка се разпадна',
|
||||
@@ -22,62 +22,62 @@ return [
|
||||
'saml_user_not_registered' => 'Потребителят :name не е регистриран и автоматичната регистрация не е достъпна',
|
||||
'saml_no_email_address' => 'Не успяхме да намерим емейл адрес, за този потребител, от информацията предоставена от външната система',
|
||||
'saml_invalid_response_id' => 'Заявката от външната система не е разпознат от процеса започнат от това приложение. Връщането назад след влизане може да породи този проблем.',
|
||||
'saml_fail_authed' => 'Влизането чрез :system не беше успешно, системата не успя да оторизира потребителя',
|
||||
'oidc_already_logged_in' => 'Already logged in',
|
||||
'oidc_user_not_registered' => 'The user :name is not registered and automatic registration is disabled',
|
||||
'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
|
||||
'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
|
||||
'saml_fail_authed' => 'Влизането чрез :system не беше успешно, системата не успя да удостовери потребителя',
|
||||
'oidc_already_logged_in' => 'Вече си вписан',
|
||||
'oidc_user_not_registered' => 'Потребителят :name не е регистриран, а автоматичната регистрация е изключена',
|
||||
'oidc_no_email_address' => 'Не можах да намеря имейл адрес за този потребител в данните, предоставени от външната удостоверителна система',
|
||||
'oidc_fail_authed' => 'Вписването чрез :system не беше успешно, тъй като системата не предостави успешна оторизация',
|
||||
'social_no_action_defined' => 'Действието не беше дефинирано',
|
||||
'social_login_bad_response' => "Възникна грешка по време на :socialAccount login: \n:error",
|
||||
'social_account_in_use' => 'Този :socialAccount вече е използван. Опитайте се да влезете чрез опцията за :socialAccount.',
|
||||
'social_account_email_in_use' => 'Този емейл адрес вече е бил използван. Ако вече имате профил, може да го свържете чрез :socialAccount от вашия профил.',
|
||||
'social_account_existing' => 'Този :socialAccount вече в свързан с вашия профил.',
|
||||
'social_account_already_used_existing' => 'Този :socialAccount вече се използва от друг потребител.',
|
||||
'social_account_not_used' => 'Този :socialAccount не е свързан с профил. Моля свържете го с вашия профил. ',
|
||||
'social_account_register_instructions' => 'Ако все още нямате профил, може да се регистрирате чрез :socialAccount опцията.',
|
||||
'social_driver_not_found' => 'Social driver not found',
|
||||
'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
|
||||
'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.',
|
||||
'social_account_not_used' => 'Социалният профил :socialAccount не е свързан с потребител. Моля, свържи го в настройките на профила си. ',
|
||||
'social_account_register_instructions' => 'Ако все още нямаш профил, може да се регистрираш чрез опцията :socialAccount.',
|
||||
'social_driver_not_found' => 'Кодът за връзка със социалната мрежа не съществува',
|
||||
'social_driver_not_configured' => 'Социалните настройки на твоя :socialAccount не са конфигурирани правилно.',
|
||||
'invite_token_expired' => 'Твоята покана е изтекла. Вместо това може да пробваш да възстановиш паролата на профила си.',
|
||||
|
||||
// System
|
||||
'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.',
|
||||
'cannot_get_image_from_url' => 'Cannot get image from :url',
|
||||
'cannot_create_thumbs' => 'The server cannot create thumbnails. Please check you have the GD PHP extension installed.',
|
||||
'server_upload_limit' => 'The server does not allow uploads of this size. Please try a smaller file size.',
|
||||
'uploaded' => 'The server does not allow uploads of this size. Please try a smaller file size.',
|
||||
'image_upload_error' => 'An error occurred uploading the image',
|
||||
'image_upload_type_error' => 'The image type being uploaded is invalid',
|
||||
'file_upload_timeout' => 'The file upload has timed out.',
|
||||
'path_not_writable' => 'Не може да се качи файл в :filePath. Увери се на сървъра, че в пътя може да се записва.',
|
||||
'cannot_get_image_from_url' => 'Не мога да взема съобщението от :url',
|
||||
'cannot_create_thumbs' => 'Сървърът не може да създаде малки изображения. Моля, увери се, че разширението GD PHP е инсталирано.',
|
||||
'server_upload_limit' => 'Сървърът не позволява качвания с такъв размер. Моля, пробвайте файл с по-малък размер.',
|
||||
'uploaded' => 'Сървърът не позволява качвания с такъв размер. Моля, пробвайте файл с по-малък размер.',
|
||||
'image_upload_error' => 'Възникна грешка при качването на изображението',
|
||||
'image_upload_type_error' => 'Типът на качваното изображение е невалиден',
|
||||
'file_upload_timeout' => 'Качването на файла изтече.',
|
||||
|
||||
// Attachments
|
||||
'attachment_not_found' => 'Attachment not found',
|
||||
'attachment_not_found' => 'Прикачения файл не е намерен',
|
||||
|
||||
// Pages
|
||||
'page_draft_autosave_fail' => 'Failed to save draft. Ensure you have internet connection before saving this page',
|
||||
'page_custom_home_deletion' => 'Cannot delete a page while it is set as a homepage',
|
||||
'page_draft_autosave_fail' => 'Неуспешно запазване на черновата. Увери се, че имаш свързаност с интернет преди да запазиш страницата',
|
||||
'page_custom_home_deletion' => 'Не мога да изтрия страницата, докато е настроена като начална',
|
||||
|
||||
// Entities
|
||||
'entity_not_found' => 'Entity not found',
|
||||
'bookshelf_not_found' => 'Bookshelf not found',
|
||||
'book_not_found' => 'Book not found',
|
||||
'page_not_found' => 'Page not found',
|
||||
'chapter_not_found' => 'Chapter not found',
|
||||
'selected_book_not_found' => 'The selected book was not found',
|
||||
'selected_book_chapter_not_found' => 'The selected Book or Chapter was not found',
|
||||
'guests_cannot_save_drafts' => 'Guests cannot save drafts',
|
||||
'entity_not_found' => 'Обектът не е намерен',
|
||||
'bookshelf_not_found' => 'Рафтът не е намерен',
|
||||
'book_not_found' => 'Книгата не е намерена',
|
||||
'page_not_found' => 'Страницата не е намерена',
|
||||
'chapter_not_found' => 'Главата не е намерена',
|
||||
'selected_book_not_found' => 'Избраната книга не е намерена',
|
||||
'selected_book_chapter_not_found' => 'Избраната книга или глава не е намерена',
|
||||
'guests_cannot_save_drafts' => 'Гостите не могат да запазват чернови',
|
||||
|
||||
// Users
|
||||
'users_cannot_delete_only_admin' => 'You cannot delete the only admin',
|
||||
'users_cannot_delete_guest' => 'You cannot delete the guest user',
|
||||
'users_cannot_delete_only_admin' => 'Не можеш да изтриеш единствения администратор',
|
||||
'users_cannot_delete_guest' => 'Не можеш да изтриеш потребителя на госта',
|
||||
|
||||
// Roles
|
||||
'role_cannot_be_edited' => 'This role cannot be edited',
|
||||
'role_system_cannot_be_deleted' => 'This role is a system role and cannot be deleted',
|
||||
'role_registration_default_cannot_delete' => 'This role cannot be deleted while set as the default registration role',
|
||||
'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.',
|
||||
'role_cannot_be_edited' => 'Ролята не може да бъде редактирана',
|
||||
'role_system_cannot_be_deleted' => 'Тази роля е системна и не може да бъде изтрита',
|
||||
'role_registration_default_cannot_delete' => 'Тази роля не може да бъде изтрита, докато е настроена по подразбиране за нови регистрации',
|
||||
'role_cannot_remove_only_admin' => 'Този потребител е единственият с присвоена администраторска роля. Приложи администраторската роля на друг потребител, преди да я премахнеш от тук.',
|
||||
|
||||
// Comments
|
||||
'comment_list' => 'An error occurred while fetching the comments.',
|
||||
'comment_list' => 'Настъпи грешка при зареждането на коментарите.',
|
||||
'cannot_add_comment_to_draft' => 'Не може да добавяте коментари към чернова.',
|
||||
'comment_add' => 'Възникна грешка при актуализиране/добавяне на коментар.',
|
||||
'comment_delete' => 'Възникна грешка при изтриването на коментара.',
|
||||
@@ -87,9 +87,9 @@ return [
|
||||
'404_page_not_found' => 'Страницата не е намерена',
|
||||
'sorry_page_not_found' => 'Страницата, която търсите не може да бъде намерена.',
|
||||
'sorry_page_not_found_permission_warning' => 'Ако смятате, че тази страница съществува, най-вероятно нямате право да я преглеждате.',
|
||||
'image_not_found' => 'Image Not Found',
|
||||
'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
|
||||
'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
|
||||
'image_not_found' => 'Изображението не е намерено',
|
||||
'image_not_found_subtitle' => 'Съжалявам, файлът на изображението, което търсиш, не може да бъде намерен.',
|
||||
'image_not_found_details' => 'Ако си очаквал/а това изображение да същестува, може да е било изтрито.',
|
||||
'return_home' => 'Назад към Начало',
|
||||
'error_occurred' => 'Възникна грешка',
|
||||
'app_down' => ':appName не е достъпно в момента',
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
return [
|
||||
|
||||
'password' => 'Паролите трябва да имат поне 8 символа и да съвпадат с потвърждението.',
|
||||
'user' => "Не можем да намерим потребител с този имейл адрес.",
|
||||
'token' => 'Кодът за зануляване на паролата е невалиден за този емейл адрес.',
|
||||
'sent' => 'Пратихме връзка за нулиране на паролата до имейла ви!',
|
||||
'reset' => 'Вашата парола е нулирана!',
|
||||
'user' => "Не може да се намери потребител с този имейл адрес.",
|
||||
'token' => 'Кодът за възстановяване на паролата е невалиден за този емейл адрес.',
|
||||
'sent' => 'На имейла ти е изпратена връзка за възстановяване на паролата ти!',
|
||||
'reset' => 'Парола ти е възстановена!',
|
||||
|
||||
];
|
||||
|
||||
@@ -10,6 +10,8 @@ return [
|
||||
'settings' => 'Настройки',
|
||||
'settings_save' => 'Запази настройките',
|
||||
'settings_save_success' => 'Настройките са записани',
|
||||
'system_version' => 'System Version',
|
||||
'categories' => 'Categories',
|
||||
|
||||
// App Settings
|
||||
'app_customization' => 'Персонализиране',
|
||||
@@ -25,8 +27,8 @@ return [
|
||||
'app_secure_images' => 'По-висока сигурност при качване на изображения',
|
||||
'app_secure_images_toggle' => 'Активиране на по-висока сигурност при качване на изображения',
|
||||
'app_secure_images_desc' => 'С цел производителност, всички изображения са публични. Тази настройка добавя случаен, труден за отгатване низ от символи пред линка на изображението. Подсигурете, че индексите на директорията не са включени за да предотвратите лесен достъп.',
|
||||
'app_editor' => 'Редактор на страница',
|
||||
'app_editor_desc' => 'Изберете кой редактор да се използва от всички потребители за да редактират страници.',
|
||||
'app_default_editor' => 'Default Page Editor',
|
||||
'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
|
||||
'app_custom_html' => 'Персонализирано съдържание на HTML шапката',
|
||||
'app_custom_html_desc' => 'Всяко съдържание, добавено тук, ще бъде поставено в долната част на секцията <head> на всяка страница. Това е удобно за преобладаващи стилове или добавяне на код за анализ.',
|
||||
'app_custom_html_disabled_notice' => 'Съдържанието на персонализираната HTML шапка е деактивирано на страницата с настройки, за да се гарантира, че евентуални лоши промени могат да бъдат върнати.',
|
||||
@@ -34,52 +36,52 @@ return [
|
||||
'app_logo_desc' => 'Това изображение трябва да е с 43px височина. <br> Големите изображения ще бъдат намалени.',
|
||||
'app_primary_color' => 'Основен цвят на приложението',
|
||||
'app_primary_color_desc' => 'Изберете основния цвят на приложението, включително на банера, бутоните и линковете.',
|
||||
'app_homepage' => 'Application Homepage',
|
||||
'app_homepage' => 'Начлна страница на приложението',
|
||||
'app_homepage_desc' => 'Изберете начална страница, която ще замени изгледа по подразбиране. Дефинираните права на страницата, която е избрана ще бъдат игнорирани.',
|
||||
'app_homepage_select' => 'Избери страница',
|
||||
'app_footer_links' => 'Футър линкове',
|
||||
'app_footer_links_desc' => 'Добави линк в съдържанието на футъра. Добавените линкове ще се показват долу в повечето страници, включително и в страниците, в които логването не е задължително. Можете да използвате заместител "trans::<key>", за да използвате дума дефинирана от системата. Пример: Използването на "trans::common.privacy_policy" ще покаже "Лични данни" или на "trans::common.terms_of_service" ще покаже "Общи условия".',
|
||||
'app_footer_links_label' => 'Link Label',
|
||||
'app_footer_links_label' => 'Надпис на връзката',
|
||||
'app_footer_links_url' => 'Линк URL',
|
||||
'app_footer_links_add' => 'Добави футър линк',
|
||||
'app_disable_comments' => 'Disable Comments',
|
||||
'app_disable_comments_toggle' => 'Disable comments',
|
||||
'app_disable_comments_desc' => 'Disables comments across all pages in the application. <br> Existing comments are not shown.',
|
||||
'app_disable_comments' => 'Изключи коментарите',
|
||||
'app_disable_comments_toggle' => 'Изключи коментарите',
|
||||
'app_disable_comments_desc' => 'Изключва коментарите във всички на страници на приложението. <br> Съществуващите коментари няма да се показват.',
|
||||
|
||||
// Color settings
|
||||
'content_colors' => 'Content Colors',
|
||||
'content_colors_desc' => 'Sets colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.',
|
||||
'bookshelf_color' => 'Shelf Color',
|
||||
'book_color' => 'Book Color',
|
||||
'chapter_color' => 'Chapter Color',
|
||||
'page_color' => 'Page Color',
|
||||
'page_draft_color' => 'Page Draft Color',
|
||||
'content_colors' => 'Цвят на съдържанието',
|
||||
'content_colors_desc' => 'Настройва цветовете за всички елементи на йерархията за организацията на страницата. Избор на цвят с яркост, близка до цветовете по подразбиране, се препоръчва за четимостта.',
|
||||
'bookshelf_color' => 'Цвят на рафта',
|
||||
'book_color' => 'Цвят на книгата',
|
||||
'chapter_color' => 'Цвят на главата',
|
||||
'page_color' => 'Цвят на страницата',
|
||||
'page_draft_color' => 'Цвят на черновата за страница',
|
||||
|
||||
// Registration Settings
|
||||
'reg_settings' => 'Registration',
|
||||
'reg_enable' => 'Enable Registration',
|
||||
'reg_enable_toggle' => 'Enable registration',
|
||||
'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
|
||||
'reg_default_role' => 'Default user role after registration',
|
||||
'reg_enable_external_warning' => 'The option above is ignored while external LDAP or SAML authentication is active. User accounts for non-existing members will be auto-created if authentication, against the external system in use, is successful.',
|
||||
'reg_email_confirmation' => 'Email Confirmation',
|
||||
'reg_email_confirmation_toggle' => 'Require email confirmation',
|
||||
'reg_confirm_email_desc' => 'If domain restriction is used then email confirmation will be required and this option will be ignored.',
|
||||
'reg_confirm_restrict_domain' => 'Domain Restriction',
|
||||
'reg_confirm_restrict_domain_desc' => 'Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application. <br> Note that users will be able to change their email addresses after successful registration.',
|
||||
'reg_confirm_restrict_domain_placeholder' => 'No restriction set',
|
||||
'reg_settings' => 'Регистрация',
|
||||
'reg_enable' => 'Включи регистрацията',
|
||||
'reg_enable_toggle' => 'Включи регистрацията',
|
||||
'reg_enable_desc' => 'Когато регистрацията е включена, потребителите ще могат да се регистрират като потребители на приложението. След регистрация на тях им се дава роля по подразбиране.',
|
||||
'reg_default_role' => 'Роля по подразбиране след регистрация',
|
||||
'reg_enable_external_warning' => 'Опцията отгоре се игнорира при активно външно LDAP или SAML удостоверяване. Ако удостоверяването от външната система е успешно, автоматично ще се създават потребителски профили за несъществуващи членове.',
|
||||
'reg_email_confirmation' => 'Имейл потвърждение',
|
||||
'reg_email_confirmation_toggle' => 'Изисквай имейл потвърждение',
|
||||
'reg_confirm_email_desc' => 'Ако се използват ограничения за домейна, ще се изисква имейл потвърждение и тази настройка ще бъде игнорирана.',
|
||||
'reg_confirm_restrict_domain' => 'Ограничения за домейна',
|
||||
'reg_confirm_restrict_domain_desc' => 'Въведи разделен със запетаи списък от имейл домейни, до които да бъде ограничена регистрацията. На потребителите ще им бъде изпратен имейл, за да потвърдят адреса, преди да могат да използват приложението. <br> Имай предвид, че потребителите ще могат да сменят имейл адресите си след успешна регистрация.',
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Няма наложени ограничения',
|
||||
|
||||
// Maintenance settings
|
||||
'maint' => 'Maintenance',
|
||||
'maint_image_cleanup' => 'Cleanup Images',
|
||||
'maint_image_cleanup_desc' => 'Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.',
|
||||
'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
|
||||
'maint_image_cleanup_run' => 'Run Cleanup',
|
||||
'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?',
|
||||
'maint_image_cleanup_success' => ':count potentially unused images found and deleted!',
|
||||
'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!',
|
||||
'maint_send_test_email' => 'Send a Test Email',
|
||||
'maint_send_test_email_desc' => 'This sends a test email to your email address specified in your profile.',
|
||||
'maint' => 'Поддръжка',
|
||||
'maint_image_cleanup' => 'Разчисти изображения',
|
||||
'maint_image_cleanup_desc' => 'Сканира съдържанието на страници и ревизиите, за да провери кои изображения и рисунки се използват и кои се повтарят. Увери се, че имаш пълни резервни копия на базата данни и на изображенията, преди да пуснеш това.',
|
||||
'maint_delete_images_only_in_revisions' => 'Също изтрий изображенията, които съществуват само в стари ревизии на страниците',
|
||||
'maint_image_cleanup_run' => 'Пусни разчистване',
|
||||
'maint_image_cleanup_warning' => 'Намерени са :count потенциално неизползвани изображения. Сигурен/на ли си, че искаш да изтриеш тези изображения?',
|
||||
'maint_image_cleanup_success' => 'Намерени и изтрити са :count потенциално неизползвани изображения!',
|
||||
'maint_image_cleanup_nothing_found' => 'Не са намерени неизползвани изображения и нищо не е изтрито!',
|
||||
'maint_send_test_email' => 'Изпрати тестови имейл',
|
||||
'maint_send_test_email_desc' => 'Това изпраща тестови имейл на имейл адреса, посочен в профила ти.',
|
||||
'maint_send_test_email_run' => 'Изпрати тестов имейл',
|
||||
'maint_send_test_email_success' => 'Имейл изпратен на :address',
|
||||
'maint_send_test_email_mail_subject' => 'Тестов Имейл',
|
||||
@@ -90,36 +92,36 @@ return [
|
||||
|
||||
// Recycle Bin
|
||||
'recycle_bin' => 'Кошче',
|
||||
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
|
||||
'recycle_bin_desc' => 'Тук може да възстановиш изтрити обекти или да ги премахнеш завинаги от системата. Този списък не е филтриран, за разлика от подобни списъци с активност в системата, където са приложени списъци за привилегии.',
|
||||
'recycle_bin_deleted_item' => 'Изтрит предмет',
|
||||
'recycle_bin_deleted_parent' => 'Parent',
|
||||
'recycle_bin_deleted_parent' => 'Родител',
|
||||
'recycle_bin_deleted_by' => 'Изтрит от',
|
||||
'recycle_bin_deleted_at' => 'Час на изтриване',
|
||||
'recycle_bin_permanently_delete' => 'Permanently Delete',
|
||||
'recycle_bin_restore' => 'Restore',
|
||||
'recycle_bin_contents_empty' => 'The recycle bin is currently empty',
|
||||
'recycle_bin_empty' => 'Empty Recycle Bin',
|
||||
'recycle_bin_empty_confirm' => 'This will permanently destroy all items in the recycle bin including content contained within each item. Are you sure you want to empty the recycle bin?',
|
||||
'recycle_bin_destroy_confirm' => 'This action will permanently delete this item, along with any child elements listed below, from the system and you will not be able to restore this content. Are you sure you want to permanently delete this item?',
|
||||
'recycle_bin_destroy_list' => 'Items to be Destroyed',
|
||||
'recycle_bin_restore_list' => 'Items to be Restored',
|
||||
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
|
||||
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
|
||||
'recycle_bin_restore_parent' => 'Restore Parent',
|
||||
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
|
||||
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
|
||||
'recycle_bin_permanently_delete' => 'Изтрий завинаги',
|
||||
'recycle_bin_restore' => 'Възстанови',
|
||||
'recycle_bin_contents_empty' => 'Кошчето е празно',
|
||||
'recycle_bin_empty' => 'Изпразни кочшето',
|
||||
'recycle_bin_empty_confirm' => 'Това ще унищожи завинаги всички обекти в кошчето, включително съдържанието във всеки обект. Сигурен/на ли си, че искаш да изпразниш кошчето?',
|
||||
'recycle_bin_destroy_confirm' => 'Това действие завинаги ще изтрие от системата този обект, както и всички негови поделементи, и няма да можеш да го възстановиш. Сигурен/на ли си, че искаш да изтриеш този обект завинаги?',
|
||||
'recycle_bin_destroy_list' => 'Обекти за унищожение',
|
||||
'recycle_bin_restore_list' => 'Обекти за възстановяване',
|
||||
'recycle_bin_restore_confirm' => 'Това действие ще възстанови изтрития обект, както и всички негови поделементи, в оригиналното им местоположение. Ако оригиналното им местоположение също е изтрито и сега се намира в кошчето, то също ще трябва да бъде възстановено.',
|
||||
'recycle_bin_restore_deleted_parent' => 'Родителският елемент на този обект също е бил изтрит. Тези ще останат изтрити, докато родителят също бъде възстановен.',
|
||||
'recycle_bin_restore_parent' => 'Възстанови родителския елемент',
|
||||
'recycle_bin_destroy_notification' => 'Изтрити общо :count обекта от кошчето.',
|
||||
'recycle_bin_restore_notification' => 'Възстановени общо :count обекта от кошчето.',
|
||||
|
||||
// Audit Log
|
||||
'audit' => 'Audit Log',
|
||||
'audit_desc' => 'This audit log displays a list of activities tracked in the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
|
||||
'audit_event_filter' => 'Event Filter',
|
||||
'audit' => 'Ревизорен журнал',
|
||||
'audit_desc' => 'Ревизорният журнал показва списък с всички дейности, следенив системата. Това е нефилтриран списък, за разлика от подобни списъци с дейности в системата, където са приложени филтри за привилегии.',
|
||||
'audit_event_filter' => 'Филтър на събитията',
|
||||
'audit_event_filter_no_filter' => 'Без филтър',
|
||||
'audit_deleted_item' => 'Изтрит предмет',
|
||||
'audit_deleted_item_name' => 'Име: :name',
|
||||
'audit_table_user' => 'Потребител',
|
||||
'audit_table_event' => 'Събитие',
|
||||
'audit_table_related' => 'Related Item or Detail',
|
||||
'audit_table_ip' => 'IP Address',
|
||||
'audit_table_related' => 'Свързан обект или детайл',
|
||||
'audit_table_ip' => 'IP адрес',
|
||||
'audit_table_date' => 'Дата на активност',
|
||||
'audit_date_from' => 'Време от',
|
||||
'audit_date_to' => 'Време до',
|
||||
@@ -139,7 +141,7 @@ return [
|
||||
'role_details' => 'Детайли на роля',
|
||||
'role_name' => 'Име на ролята',
|
||||
'role_desc' => 'Кратко описание на ролята',
|
||||
'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
|
||||
'role_mfa_enforced' => 'Изисква многофакторно удостоверяване',
|
||||
'role_external_auth_id' => 'Външни ауторизиращи ID-a',
|
||||
'role_system' => 'Настойки за достъп на системата',
|
||||
'role_manage_users' => 'Управление на потребители',
|
||||
@@ -149,14 +151,15 @@ return [
|
||||
'role_manage_page_templates' => 'Управление на шаблони на страници',
|
||||
'role_access_api' => 'Достъп до API на системата',
|
||||
'role_manage_settings' => 'Управление на настройките на приложението',
|
||||
'role_export_content' => 'Export content',
|
||||
'role_export_content' => 'Експортирай съдържанието',
|
||||
'role_editor_change' => 'Change page editor',
|
||||
'role_asset' => 'Настройки за достъп до активи',
|
||||
'roles_system_warning' => 'Важно: Добавянето на потребител в някое от горните три роли може да му позволи да промени собствените си права или правата на другите в системата. Възлагайте тези роли само на доверени потребители.',
|
||||
'role_asset_desc' => 'Тези настройки за достъп контролират достъпа по подразбиране до активите в системата. Настройките за достъп до книги, глави и страници ще отменят тези настройки.',
|
||||
'role_asset_admins' => 'Администраторите автоматично получават достъп до цялото съдържание, но тези опции могат да показват или скриват опциите за потребителския интерфейс.',
|
||||
'role_all' => 'Всички',
|
||||
'role_own' => 'Собствени',
|
||||
'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
|
||||
'role_controlled_by_asset' => 'Контролирани от актива, към който са качени',
|
||||
'role_save' => 'Запази ролята',
|
||||
'role_update_success' => 'Ролята беше успешно актуализирана',
|
||||
'role_users' => 'Потребители в тази роля',
|
||||
@@ -169,94 +172,94 @@ return [
|
||||
'users_search' => 'Търси Потребители',
|
||||
'users_latest_activity' => 'Последна активност',
|
||||
'users_details' => 'Потребителски детайли',
|
||||
'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
|
||||
'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
|
||||
'users_role' => 'User Roles',
|
||||
'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
|
||||
'users_password' => 'User Password',
|
||||
'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 8 characters long.',
|
||||
'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
|
||||
'users_send_invite_option' => 'Send user invite email',
|
||||
'users_external_auth_id' => 'External Authentication ID',
|
||||
'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your external authentication system.',
|
||||
'users_password_warning' => 'Only fill the below if you would like to change your password.',
|
||||
'users_system_public' => 'This user represents any guest users that visit your instance. It cannot be used to log in but is assigned automatically.',
|
||||
'users_details_desc' => 'Настрой име и имейл адрес за този потребител. Имейл адресът ще се използва за вписване в приложението.',
|
||||
'users_details_desc_no_email' => 'Настрой име за този потребител, за да могат другите да го разпознават.',
|
||||
'users_role' => 'Потребителски роли',
|
||||
'users_role_desc' => 'Настрой ролите, които ще бъдат присвоени на този потребител. Ако му бъдат присвоени няколко роли, привилегиите от тях ще се насложат и потребителят ще получи всички привилегии на зададените роли.',
|
||||
'users_password' => 'Потребителска парола',
|
||||
'users_password_desc' => 'Настрой парола за вписване в приложението. Тя трябва да бъде дълга поне 8 знака.',
|
||||
'users_send_invite_text' => 'Можеш да изпратиш на потребителя покана по имейл, след което той ще може да настрои своя собствена парола. В противен случай, ти също можеш да настроиш паролата му.',
|
||||
'users_send_invite_option' => 'Изпрати на потребителя имейл покана',
|
||||
'users_external_auth_id' => 'Външен номер за удостоверяване',
|
||||
'users_external_auth_id_desc' => 'Това е номерът, използван за сверяване на потребители при комуникация с конфигурираната външна система за удостоверяване.',
|
||||
'users_password_warning' => 'Попълни отдолу само ако желаеш да смениш паролата си.',
|
||||
'users_system_public' => 'Този потребител представлява всеки гост, който посещава това приложение. Потребителят не може да се използва за вписване, а вместо това се присвоява автоматично.',
|
||||
'users_delete' => 'Изтрий потребител',
|
||||
'users_delete_named' => 'Изтрий потребителя :userName',
|
||||
'users_delete_warning' => 'Това изцяло ще изтрие този потребител с името \':userName\' от системата.',
|
||||
'users_delete_confirm' => 'Сигурни ли сте, че искате да изтриете този потребител?',
|
||||
'users_migrate_ownership' => 'Мигрирайте собствеността на сайта',
|
||||
'users_migrate_ownership_desc' => 'Select a user here if you want another user to become the owner of all items currently owned by this user.',
|
||||
'users_migrate_ownership_desc' => 'Тук избери потребител, ако желаеш друг да стане собственик на всички обекти, които към момента са притежавани от този потребител.',
|
||||
'users_none_selected' => 'Няма избрани потребители',
|
||||
'users_edit' => 'Edit User',
|
||||
'users_edit_profile' => 'Edit Profile',
|
||||
'users_avatar' => 'User Avatar',
|
||||
'users_avatar_desc' => 'Select an image to represent this user. This should be approx 256px square.',
|
||||
'users_preferred_language' => 'Preferred Language',
|
||||
'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
|
||||
'users_social_accounts' => 'Social Accounts',
|
||||
'users_social_accounts_info' => 'Here you can connect your other accounts for quicker and easier login. Disconnecting an account here does not revoke previously authorized access. Revoke access from your profile settings on the connected social account.',
|
||||
'users_social_connect' => 'Connect Account',
|
||||
'users_social_disconnect' => 'Disconnect Account',
|
||||
'users_social_connected' => ':socialAccount account was successfully attached to your profile.',
|
||||
'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.',
|
||||
'users_api_tokens' => 'API Tokens',
|
||||
'users_api_tokens_none' => 'No API tokens have been created for this user',
|
||||
'users_api_tokens_create' => 'Create Token',
|
||||
'users_api_tokens_expires' => 'Expires',
|
||||
'users_api_tokens_docs' => 'API Documentation',
|
||||
'users_mfa' => 'Multi-Factor Authentication',
|
||||
'users_mfa_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.',
|
||||
'users_mfa_x_methods' => ':count method configured|:count methods configured',
|
||||
'users_mfa_configure' => 'Configure Methods',
|
||||
'users_edit' => 'Редактирай потребител',
|
||||
'users_edit_profile' => 'Редактирай профил',
|
||||
'users_avatar' => 'Потребителски аватар',
|
||||
'users_avatar_desc' => 'Избери изображение, което да представлява този потребител. То трябва да бъде квадрат с размер приблизително 256 пиксела.',
|
||||
'users_preferred_language' => 'Предпочитан език',
|
||||
'users_preferred_language_desc' => 'Тази настройка ще промени езика за потребителския интерфейс на приложението. Това няма да се отрази на създаденото от потребителите съдържание.',
|
||||
'users_social_accounts' => 'Социални профили',
|
||||
'users_social_accounts_info' => 'Тук можеш да свържеш другите си профили за по-бързо и лесно вписване. Отвързването на профил тук няма да анулира предишно удостоверен достъп. Вместо това, спри достъпа от настройките на профила си в свързаната социална мрежа.',
|
||||
'users_social_connect' => 'Свържи профил',
|
||||
'users_social_disconnect' => 'Отвържи профил',
|
||||
'users_social_connected' => 'Профилът :socialAccount беше успешно свързан с профила ти.',
|
||||
'users_social_disconnected' => 'Профилът :socialAccount беше успешно отвързан от профила ти.',
|
||||
'users_api_tokens' => 'API маркери',
|
||||
'users_api_tokens_none' => 'Няма създадени API маркери за този потребител',
|
||||
'users_api_tokens_create' => 'Създай маркер',
|
||||
'users_api_tokens_expires' => 'Изтича на',
|
||||
'users_api_tokens_docs' => 'Документация на API',
|
||||
'users_mfa' => 'Многофакторно удостоверяване',
|
||||
'users_mfa_desc' => 'Настрой многофакторно удостверяване като втори слой сигурност на твоя профил.',
|
||||
'users_mfa_x_methods' => ':count метод е настроен|:count методи са настроени',
|
||||
'users_mfa_configure' => 'Конфигурирай методи',
|
||||
|
||||
// API Tokens
|
||||
'user_api_token_create' => 'Create API Token',
|
||||
'user_api_token_name' => 'Name',
|
||||
'user_api_token_name_desc' => 'Give your token a readable name as a future reminder of its intended purpose.',
|
||||
'user_api_token_expiry' => 'Expiry Date',
|
||||
'user_api_token_expiry_desc' => 'Set a date at which this token expires. After this date, requests made using this token will no longer work. Leaving this field blank will set an expiry 100 years into the future.',
|
||||
'user_api_token_create_secret_message' => 'Immediately after creating this token a "Token ID" & "Token Secret" will be generated and displayed. The secret will only be shown a single time so be sure to copy the value to somewhere safe and secure before proceeding.',
|
||||
'user_api_token_create_success' => 'API token successfully created',
|
||||
'user_api_token_update_success' => 'API token successfully updated',
|
||||
'user_api_token' => 'API Token',
|
||||
'user_api_token_id' => 'Token ID',
|
||||
'user_api_token_id_desc' => 'This is a non-editable system generated identifier for this token which will need to be provided in API requests.',
|
||||
'user_api_token_secret' => 'Token Secret',
|
||||
'user_api_token_secret_desc' => 'This is a system generated secret for this token which will need to be provided in API requests. This will only be displayed this one time so copy this value to somewhere safe and secure.',
|
||||
'user_api_token_created' => 'Token created :timeAgo',
|
||||
'user_api_token_updated' => 'Token updated :timeAgo',
|
||||
'user_api_token_delete' => 'Delete Token',
|
||||
'user_api_token_delete_warning' => 'This will fully delete this API token with the name \':tokenName\' from the system.',
|
||||
'user_api_token_delete_confirm' => 'Are you sure you want to delete this API token?',
|
||||
'user_api_token_delete_success' => 'API token successfully deleted',
|
||||
'user_api_token_create' => 'Създай API маркер',
|
||||
'user_api_token_name' => 'Име',
|
||||
'user_api_token_name_desc' => 'Дай на маркера си четимо име като бъдещо напомняне за предназначението му.',
|
||||
'user_api_token_expiry' => 'Дата на изтичане',
|
||||
'user_api_token_expiry_desc' => 'Настрой дата на изтичане на този маркер. След тази дата, заявки направени с този маркер вече няма да работят. Ако оставиш това поле празно, маркерът ще изтече след 100 години.',
|
||||
'user_api_token_create_secret_message' => 'Веднага след създаването на този маркер ще се генерират и покажат "Номер на маркер" и "Тайна на маркер". Тайната ще бъде показана само веднъж, така че се увери, че си я копирал на сигурно място, преди да продължиш.',
|
||||
'user_api_token_create_success' => 'API маркерът е създаден успешно',
|
||||
'user_api_token_update_success' => 'API маркерът е редактиран успешно',
|
||||
'user_api_token' => 'API маркер',
|
||||
'user_api_token_id' => 'Номер на маркер',
|
||||
'user_api_token_id_desc' => 'Това е нередактируем, системно генериран идентификатор за този маркер, който ще бъде необходимо да бъде предоставян в API заявките.',
|
||||
'user_api_token_secret' => 'Тайна на маркер',
|
||||
'user_api_token_secret_desc' => 'Това е системно генерирана тайна за този маркер, която ще бъде необходимо да бъде предоставяна в API заявки. Тайната ще бъде показана само веднъж, така че се увери, че си я копирал на сигурно място.',
|
||||
'user_api_token_created' => 'Маркерът е създаден :timeAgo',
|
||||
'user_api_token_updated' => 'Маркерът е редактиран :timeAgo',
|
||||
'user_api_token_delete' => 'Изтрий маркер',
|
||||
'user_api_token_delete_warning' => 'Това ще изтрие напълно API маркерът с име \':tokenName\' от системата.',
|
||||
'user_api_token_delete_confirm' => 'Сигурен/на ли си, че искаш да изтриеш този API маркер?',
|
||||
'user_api_token_delete_success' => 'API маркерът е изтрит успешно',
|
||||
|
||||
// Webhooks
|
||||
'webhooks' => 'Webhooks',
|
||||
'webhooks_create' => 'Create New Webhook',
|
||||
'webhooks_none_created' => 'No webhooks have yet been created.',
|
||||
'webhooks_edit' => 'Edit Webhook',
|
||||
'webhooks_save' => 'Save Webhook',
|
||||
'webhooks_details' => 'Webhook Details',
|
||||
'webhooks_details_desc' => 'Provide a user friendly name and a POST endpoint as a location for the webhook data to be sent to.',
|
||||
'webhooks_events' => 'Webhook Events',
|
||||
'webhooks_events_desc' => 'Select all the events that should trigger this webhook to be called.',
|
||||
'webhooks_events_warning' => 'Keep in mind that these events will be triggered for all selected events, even if custom permissions are applied. Ensure that use of this webhook won\'t expose confidential content.',
|
||||
'webhooks_events_all' => 'All system events',
|
||||
'webhooks_name' => 'Webhook Name',
|
||||
'webhooks_timeout' => 'Webhook Request Timeout (Seconds)',
|
||||
'webhooks_endpoint' => 'Webhook Endpoint',
|
||||
'webhooks_active' => 'Webhook Active',
|
||||
'webhook_events_table_header' => 'Events',
|
||||
'webhooks_delete' => 'Delete Webhook',
|
||||
'webhooks_delete_warning' => 'This will fully delete this webhook, with the name \':webhookName\', from the system.',
|
||||
'webhooks_delete_confirm' => 'Are you sure you want to delete this webhook?',
|
||||
'webhooks_format_example' => 'Webhook Format Example',
|
||||
'webhooks_format_example_desc' => 'Webhook data is sent as a POST request to the configured endpoint as JSON following the format below. The "related_item" and "url" properties are optional and will depend on the type of event triggered.',
|
||||
'webhooks_status' => 'Webhook Status',
|
||||
'webhooks_last_called' => 'Last Called:',
|
||||
'webhooks_last_errored' => 'Last Errored:',
|
||||
'webhooks_last_error_message' => 'Last Error Message:',
|
||||
'webhooks' => 'Уебкука',
|
||||
'webhooks_create' => 'Създай нова уебкука',
|
||||
'webhooks_none_created' => 'Няма създадени уебкуки.',
|
||||
'webhooks_edit' => 'Редактирай уебкука',
|
||||
'webhooks_save' => 'Запази уебкука',
|
||||
'webhooks_details' => 'Подробности за уебкука',
|
||||
'webhooks_details_desc' => 'Въведи име и POST крайна точка като местоположение, на което уебкуката да изпраща данни.',
|
||||
'webhooks_events' => 'Събития на уебкуката',
|
||||
'webhooks_events_desc' => 'Избери всички събития, които ще задействат съответната уебкука.',
|
||||
'webhooks_events_warning' => 'Имай предвид, че тези събития ще се задействат за всички избрани събития, дори при приложени специфични привилегии. Увери се, че употребата на тази уебкука няма да разкрие чувствително съдържание.',
|
||||
'webhooks_events_all' => 'Всички системни събития',
|
||||
'webhooks_name' => 'Име на уебкука',
|
||||
'webhooks_timeout' => 'Време за изтичане на заявката не уебкуката (в секунди)',
|
||||
'webhooks_endpoint' => 'Крайна точка на уебкуката',
|
||||
'webhooks_active' => 'Уебкуката е активна',
|
||||
'webhook_events_table_header' => 'Събития',
|
||||
'webhooks_delete' => 'Изтрий уебкуката',
|
||||
'webhooks_delete_warning' => 'Това ще изтрие изцяло уебкуката с име \':webhookName\' от системата.',
|
||||
'webhooks_delete_confirm' => 'Сигурен/на ли си, че искаш да изтриеш тази уебкука?',
|
||||
'webhooks_format_example' => 'Примерен формат на уебкука',
|
||||
'webhooks_format_example_desc' => 'Данните на уебкуката се изпращат като POST заявки към конфигурираната крайна точка като JSON, следвайки формата отдолу. Свойствата "related_item" и "url" са по желание и зависят от типа на задействаното събитие.',
|
||||
'webhooks_status' => 'Статус на уебкука',
|
||||
'webhooks_last_called' => 'Последно извикан на:',
|
||||
'webhooks_last_errored' => 'Последна грешка на:',
|
||||
'webhooks_last_error_message' => 'Последно съобщение за грешка:',
|
||||
|
||||
|
||||
//! If editing translations files directly please ignore this in all
|
||||
@@ -275,6 +278,8 @@ return [
|
||||
'es' => 'Español',
|
||||
'es_AR' => 'Español Argentina',
|
||||
'et' => 'Eesti keel',
|
||||
'eu' => 'Euskara',
|
||||
'fa' => 'فارسی',
|
||||
'fr' => 'Français',
|
||||
'he' => 'עברית',
|
||||
'hr' => 'Hrvatski',
|
||||
|
||||
@@ -15,13 +15,13 @@ return [
|
||||
'alpha_dash' => ':attribute може да съдържа само букви, числа, тире и долна черта.',
|
||||
'alpha_num' => ':attribute може да съдържа само букви и числа.',
|
||||
'array' => ':attribute трябва да е масив (array).',
|
||||
'backup_codes' => 'The provided code is not valid or has already been used.',
|
||||
'backup_codes' => 'Предоставеният код не е валиден или вече е бил използван.',
|
||||
'before' => ':attribute трябва да е дата след :date.',
|
||||
'between' => [
|
||||
'numeric' => ':attribute трябва да е между :min и :max.',
|
||||
'file' => ':attribute трябва да е между :min и :max килобайта.',
|
||||
'string' => 'Дължината на :attribute трябва да бъде между :min и :max символа.',
|
||||
'array' => 'The :attribute must have between :min and :max items.',
|
||||
'array' => 'Атрибутът :attribute трябва да има между :min и :max елемента.',
|
||||
],
|
||||
'boolean' => 'Полето :attribute трябва да съдържа булева стойност (true или false).',
|
||||
'confirmed' => 'Потвърждението на :attribute не съвпада.',
|
||||
@@ -32,19 +32,19 @@ return [
|
||||
'digits_between' => ':attribute трябва да бъде с дължина между :min и :max цифри.',
|
||||
'email' => ':attribute трябва да бъде валиден имейл адрес.',
|
||||
'ends_with' => ':attribute трябва да свършва с един от следните символи: :values',
|
||||
'file' => 'The :attribute must be provided as a valid file.',
|
||||
'file' => 'Атрибутът :attribute трябва да бъде предоставен като валиден файл.',
|
||||
'filled' => 'Полето :attribute е задължителен.',
|
||||
'gt' => [
|
||||
'numeric' => ':attribute трябва да бъде по-голям от :value.',
|
||||
'file' => 'Големината на :attribute трябва да бъде по-голямо от :value килобайта.',
|
||||
'string' => 'Дължината на :attribute трябва да бъде по-голямо от :value символа.',
|
||||
'array' => 'The :attribute must have more than :value items.',
|
||||
'array' => 'Атрибутът :attribute трябва да има повече от :value елемента.',
|
||||
],
|
||||
'gte' => [
|
||||
'numeric' => 'The :attribute must be greater than or equal :value.',
|
||||
'numeric' => 'Атрибутът :attribute трябва бъде равен на или по-голям от :value.',
|
||||
'file' => 'Големината на :attribute трябва да бъде по-голямо или равно на :value килобайта.',
|
||||
'string' => 'Дължината на :attribute трябва да бъде по-голямо или равно на :value символа.',
|
||||
'array' => 'The :attribute must have :value items or more.',
|
||||
'array' => 'Атрибутът :attribute трябва да има поне :value елемента или повече.',
|
||||
],
|
||||
'exists' => 'Избраният :attribute е невалиден.',
|
||||
'image' => ':attribute трябва да e изображение.',
|
||||
@@ -59,56 +59,56 @@ return [
|
||||
'numeric' => ':attribute трябва да бъде по-малко от :value.',
|
||||
'file' => 'Големината на :attribute трябва да бъде по-малко от :value килобайта.',
|
||||
'string' => 'Дължината на :attribute трябва да бъде по-малко от :value символа.',
|
||||
'array' => 'The :attribute must have less than :value items.',
|
||||
'array' => 'Атрибутът :attribute трябва да има по-малко от :value елемента.',
|
||||
],
|
||||
'lte' => [
|
||||
'numeric' => ':attribute трябва да бъде по-малко или равно на :value.',
|
||||
'file' => 'Големината на :attribute трябва да бъде по-малко или равно на :value килобайта.',
|
||||
'string' => 'Дължината на :attribute трябва да бъде по-малко или равно на :value символа.',
|
||||
'array' => 'The :attribute must not have more than :value items.',
|
||||
'array' => 'Атрибутът :attribute не трябва да има повече от :value елемента.',
|
||||
],
|
||||
'max' => [
|
||||
'numeric' => ':attribute не трябва да бъде по-голям от :max.',
|
||||
'file' => 'Големината на :attribute не може да бъде по-голямо от :value килобайта.',
|
||||
'string' => 'Дължината на :attribute не може да бъде по-голямо от :value символа.',
|
||||
'array' => 'The :attribute may not have more than :max items.',
|
||||
'array' => 'Атрибутът :attribute не може да има повече от :max елемента.',
|
||||
],
|
||||
'mimes' => 'The :attribute must be a file of type: :values.',
|
||||
'mimes' => 'Атрибутът :attribute трябва да бъде файл от тип: :values.',
|
||||
'min' => [
|
||||
'numeric' => 'The :attribute must be at least :min.',
|
||||
'file' => 'The :attribute must be at least :min kilobytes.',
|
||||
'string' => 'The :attribute must be at least :min characters.',
|
||||
'array' => 'The :attribute must have at least :min items.',
|
||||
'numeric' => 'Атрибутът :attribute трябва да бъде поне :min.',
|
||||
'file' => 'Атрибутът :attribute трябва да бъде поне :min килобайта.',
|
||||
'string' => 'Атрибутът :attribute трябва да бъде съдържа поне :min символа.',
|
||||
'array' => 'Атрибутът :attribute трябва да има поне :min елемента.',
|
||||
],
|
||||
'not_in' => 'The selected :attribute is invalid.',
|
||||
'not_regex' => 'The :attribute format is invalid.',
|
||||
'numeric' => 'The :attribute must be a number.',
|
||||
'regex' => 'The :attribute format is invalid.',
|
||||
'required' => 'The :attribute field is required.',
|
||||
'required_if' => 'The :attribute field is required when :other is :value.',
|
||||
'required_with' => 'The :attribute field is required when :values is present.',
|
||||
'required_with_all' => 'The :attribute field is required when :values is present.',
|
||||
'required_without' => 'The :attribute field is required when :values is not present.',
|
||||
'required_without_all' => 'The :attribute field is required when none of :values are present.',
|
||||
'same' => 'The :attribute and :other must match.',
|
||||
'safe_url' => 'The provided link may not be safe.',
|
||||
'not_in' => 'Избраният :attribute не е валиден.',
|
||||
'not_regex' => 'Форматът на :attribute не е валиден.',
|
||||
'numeric' => 'Атрибутът :attribute трябва да бъде число.',
|
||||
'regex' => 'Форматът на :attribute не е валиден.',
|
||||
'required' => 'Полето :attribute е задължително.',
|
||||
'required_if' => 'Полето :attribute е задължително, когато :other е :value.',
|
||||
'required_with' => 'Полето :attribute е задължително, когато :values е налично.',
|
||||
'required_with_all' => 'Полето :attribute е задължително, когато :values са налични.',
|
||||
'required_without' => 'Полето :attribute е задължително, когато :values не е налично.',
|
||||
'required_without_all' => 'Полето :attribute е задължително, когато никоя стойност от :values не е налична.',
|
||||
'same' => 'Атрибутът :attribute и :other трябва да си съвпадат.',
|
||||
'safe_url' => 'Предоставеният линк може да не е сигурен.',
|
||||
'size' => [
|
||||
'numeric' => 'The :attribute must be :size.',
|
||||
'file' => 'The :attribute must be :size kilobytes.',
|
||||
'string' => 'The :attribute must be :size characters.',
|
||||
'array' => 'The :attribute must contain :size items.',
|
||||
'numeric' => 'Атрибутът :attribute трябва да бъде :size.',
|
||||
'file' => 'Атрибутът :attribute трябва да бъде :size килобайта.',
|
||||
'string' => 'Атрибутът :attribute трябва да бъде с дължина :size знака.',
|
||||
'array' => 'Атрибутът :attribute трябва да съдържа :size елемента.',
|
||||
],
|
||||
'string' => 'The :attribute must be a string.',
|
||||
'timezone' => 'The :attribute must be a valid zone.',
|
||||
'totp' => 'The provided code is not valid or has expired.',
|
||||
'unique' => 'The :attribute has already been taken.',
|
||||
'url' => 'The :attribute format is invalid.',
|
||||
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
|
||||
'string' => 'Атрибутът :attribute трябва да бъде текст.',
|
||||
'timezone' => 'Атрибутът :attribute трябва да бъде валидна зона.',
|
||||
'totp' => 'Предоставеният код не е валиден или е изтекъл.',
|
||||
'unique' => 'Атрибутът :attribute вече е зает.',
|
||||
'url' => 'Форматът на :attribute не е валиден.',
|
||||
'uploaded' => 'Файлът не можа да бъде качен. Сървърът може да не приема файлове с такъв размер.',
|
||||
|
||||
// Custom validation lines
|
||||
'custom' => [
|
||||
'password-confirm' => [
|
||||
'required_with' => 'Password confirmation required',
|
||||
'required_with' => 'Изисква се потвърждение на паролата',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ return [
|
||||
'width' => 'Width',
|
||||
'height' => 'Height',
|
||||
'More' => 'More',
|
||||
'select' => 'Select...',
|
||||
|
||||
// Toolbar
|
||||
'formats' => 'Formats',
|
||||
@@ -52,9 +53,10 @@ return [
|
||||
'align_left' => 'Align left',
|
||||
'align_center' => 'Align center',
|
||||
'align_right' => 'Align right',
|
||||
'align_justify' => 'Align justify',
|
||||
'align_justify' => 'Justify',
|
||||
'list_bullet' => 'Bullet list',
|
||||
'list_numbered' => 'Numbered list',
|
||||
'list_task' => 'Task list',
|
||||
'indent_increase' => 'Increase indent',
|
||||
'indent_decrease' => 'Decrease indent',
|
||||
'table' => 'Table',
|
||||
@@ -91,7 +93,10 @@ return [
|
||||
'cell_properties_title' => 'Cell Properties',
|
||||
'cell_type' => 'Cell type',
|
||||
'cell_type_cell' => 'Cell',
|
||||
'cell_scope' => 'Scope',
|
||||
'cell_type_header' => 'Header cell',
|
||||
'merge_cells' => 'Merge cells',
|
||||
'split_cell' => 'Split cell',
|
||||
'table_row_group' => 'Row Group',
|
||||
'table_column_group' => 'Column Group',
|
||||
'horizontal_align' => 'Horizontal align',
|
||||
@@ -119,6 +124,16 @@ return [
|
||||
'caption' => 'Caption',
|
||||
'show_caption' => 'Show caption',
|
||||
'constrain' => 'Constrain proportions',
|
||||
'cell_border_solid' => 'Solid',
|
||||
'cell_border_dotted' => 'Dotted',
|
||||
'cell_border_dashed' => 'Dashed',
|
||||
'cell_border_double' => 'Double',
|
||||
'cell_border_groove' => 'Groove',
|
||||
'cell_border_ridge' => 'Ridge',
|
||||
'cell_border_inset' => 'Inset',
|
||||
'cell_border_outset' => 'Outset',
|
||||
'cell_border_none' => 'None',
|
||||
'cell_border_hidden' => 'Hidden',
|
||||
|
||||
// Images, links, details/summary & embed
|
||||
'source' => 'Source',
|
||||
@@ -139,12 +154,14 @@ return [
|
||||
'toggle_label' => 'Toggle label',
|
||||
|
||||
// About view
|
||||
'about' => 'About the editor',
|
||||
'about_title' => 'About the WYSIWYG Editor',
|
||||
'editor_license' => 'Editor License & Copyright',
|
||||
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.',
|
||||
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
|
||||
'save_continue' => 'Save Page & Continue',
|
||||
'callouts_cycle' => '(Keep pressing to toggle through types)',
|
||||
'link_selector' => 'Link to content',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcut' => 'Shortcut',
|
||||
'shortcuts_intro' => 'The following shortcuts are available in the editor:',
|
||||
|
||||
@@ -196,9 +196,19 @@ return [
|
||||
'pages_edit_draft_save_at' => 'Draft saved at ',
|
||||
'pages_edit_delete_draft' => 'Delete Draft',
|
||||
'pages_edit_discard_draft' => 'Discard Draft',
|
||||
'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
|
||||
'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
|
||||
'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
|
||||
'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
|
||||
'pages_edit_set_changelog' => 'Set Changelog',
|
||||
'pages_edit_enter_changelog_desc' => 'Enter a brief description of the changes you\'ve made',
|
||||
'pages_edit_enter_changelog' => 'Enter Changelog',
|
||||
'pages_editor_switch_title' => 'Switch Editor',
|
||||
'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
|
||||
'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
|
||||
'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
|
||||
'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
|
||||
'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
|
||||
'pages_save' => 'Save Page',
|
||||
'pages_title' => 'Page Title',
|
||||
'pages_name' => 'Page Name',
|
||||
@@ -225,6 +235,7 @@ return [
|
||||
'pages_revisions_number' => '#',
|
||||
'pages_revisions_numbered' => 'Revision #:id',
|
||||
'pages_revisions_numbered_changes' => 'Revision #:id Changes',
|
||||
'pages_revisions_editor' => 'Editor Type',
|
||||
'pages_revisions_changelog' => 'Changelog',
|
||||
'pages_revisions_changes' => 'Changes',
|
||||
'pages_revisions_current' => 'Trenutna verzija',
|
||||
|
||||
@@ -10,6 +10,8 @@ return [
|
||||
'settings' => 'Settings',
|
||||
'settings_save' => 'Save Settings',
|
||||
'settings_save_success' => 'Settings saved',
|
||||
'system_version' => 'System Version',
|
||||
'categories' => 'Categories',
|
||||
|
||||
// App Settings
|
||||
'app_customization' => 'Customization',
|
||||
@@ -25,8 +27,8 @@ return [
|
||||
'app_secure_images' => 'Higher Security Image Uploads',
|
||||
'app_secure_images_toggle' => 'Enable higher security image uploads',
|
||||
'app_secure_images_desc' => 'For performance reasons, all images are public. This option adds a random, hard-to-guess string in front of image urls. Ensure directory indexes are not enabled to prevent easy access.',
|
||||
'app_editor' => 'Page Editor',
|
||||
'app_editor_desc' => 'Select which editor will be used by all users to edit pages.',
|
||||
'app_default_editor' => 'Default Page Editor',
|
||||
'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
|
||||
'app_custom_html' => 'Custom HTML Head Content',
|
||||
'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the <head> section of every page. This is handy for overriding styles or adding analytics code.',
|
||||
'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
|
||||
@@ -150,6 +152,7 @@ return [
|
||||
'role_access_api' => 'Access system API',
|
||||
'role_manage_settings' => 'Manage app settings',
|
||||
'role_export_content' => 'Export content',
|
||||
'role_editor_change' => 'Change page editor',
|
||||
'role_asset' => 'Asset Permissions',
|
||||
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
|
||||
'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
|
||||
@@ -275,6 +278,8 @@ return [
|
||||
'es' => 'Español',
|
||||
'es_AR' => 'Español Argentina',
|
||||
'et' => 'Eesti keel',
|
||||
'eu' => 'Euskara',
|
||||
'fa' => 'فارسی',
|
||||
'fr' => 'Français',
|
||||
'he' => 'עברית',
|
||||
'hr' => 'Hrvatski',
|
||||
|
||||
@@ -24,6 +24,7 @@ return [
|
||||
'width' => 'Width',
|
||||
'height' => 'Height',
|
||||
'More' => 'More',
|
||||
'select' => 'Select...',
|
||||
|
||||
// Toolbar
|
||||
'formats' => 'Formats',
|
||||
@@ -52,9 +53,10 @@ return [
|
||||
'align_left' => 'Align left',
|
||||
'align_center' => 'Align center',
|
||||
'align_right' => 'Align right',
|
||||
'align_justify' => 'Align justify',
|
||||
'align_justify' => 'Justify',
|
||||
'list_bullet' => 'Bullet list',
|
||||
'list_numbered' => 'Numbered list',
|
||||
'list_task' => 'Task list',
|
||||
'indent_increase' => 'Increase indent',
|
||||
'indent_decrease' => 'Decrease indent',
|
||||
'table' => 'Table',
|
||||
@@ -91,7 +93,10 @@ return [
|
||||
'cell_properties_title' => 'Cell Properties',
|
||||
'cell_type' => 'Cell type',
|
||||
'cell_type_cell' => 'Cell',
|
||||
'cell_scope' => 'Scope',
|
||||
'cell_type_header' => 'Header cell',
|
||||
'merge_cells' => 'Merge cells',
|
||||
'split_cell' => 'Split cell',
|
||||
'table_row_group' => 'Row Group',
|
||||
'table_column_group' => 'Column Group',
|
||||
'horizontal_align' => 'Horizontal align',
|
||||
@@ -119,6 +124,16 @@ return [
|
||||
'caption' => 'Caption',
|
||||
'show_caption' => 'Show caption',
|
||||
'constrain' => 'Constrain proportions',
|
||||
'cell_border_solid' => 'Solid',
|
||||
'cell_border_dotted' => 'Dotted',
|
||||
'cell_border_dashed' => 'Dashed',
|
||||
'cell_border_double' => 'Double',
|
||||
'cell_border_groove' => 'Groove',
|
||||
'cell_border_ridge' => 'Ridge',
|
||||
'cell_border_inset' => 'Inset',
|
||||
'cell_border_outset' => 'Outset',
|
||||
'cell_border_none' => 'None',
|
||||
'cell_border_hidden' => 'Hidden',
|
||||
|
||||
// Images, links, details/summary & embed
|
||||
'source' => 'Source',
|
||||
@@ -139,12 +154,14 @@ return [
|
||||
'toggle_label' => 'Toggle label',
|
||||
|
||||
// About view
|
||||
'about' => 'About the editor',
|
||||
'about_title' => 'About the WYSIWYG Editor',
|
||||
'editor_license' => 'Editor License & Copyright',
|
||||
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.',
|
||||
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
|
||||
'save_continue' => 'Save Page & Continue',
|
||||
'callouts_cycle' => '(Keep pressing to toggle through types)',
|
||||
'link_selector' => 'Link to content',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcut' => 'Shortcut',
|
||||
'shortcuts_intro' => 'The following shortcuts are available in the editor:',
|
||||
|
||||
@@ -196,9 +196,19 @@ return [
|
||||
'pages_edit_draft_save_at' => 'Esborrany desat ',
|
||||
'pages_edit_delete_draft' => 'Suprimeix l\'esborrany',
|
||||
'pages_edit_discard_draft' => 'Descarta l\'esborrany',
|
||||
'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
|
||||
'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
|
||||
'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
|
||||
'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
|
||||
'pages_edit_set_changelog' => 'Defineix el registre de canvis',
|
||||
'pages_edit_enter_changelog_desc' => 'Introduïu una breu descripció dels canvis que heu fet',
|
||||
'pages_edit_enter_changelog' => 'Introduïu un registre de canvis',
|
||||
'pages_editor_switch_title' => 'Switch Editor',
|
||||
'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
|
||||
'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
|
||||
'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
|
||||
'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
|
||||
'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
|
||||
'pages_save' => 'Desa la pàgina',
|
||||
'pages_title' => 'Títol de la pàgina',
|
||||
'pages_name' => 'Nom de la pàgina',
|
||||
@@ -225,6 +235,7 @@ return [
|
||||
'pages_revisions_number' => 'Núm. ',
|
||||
'pages_revisions_numbered' => 'Revisió núm. :id',
|
||||
'pages_revisions_numbered_changes' => 'Canvis de la revisió núm. :id',
|
||||
'pages_revisions_editor' => 'Editor Type',
|
||||
'pages_revisions_changelog' => 'Registre de canvis',
|
||||
'pages_revisions_changes' => 'Canvis',
|
||||
'pages_revisions_current' => 'Versió actual',
|
||||
|
||||
@@ -10,6 +10,8 @@ return [
|
||||
'settings' => 'Configuració',
|
||||
'settings_save' => 'Desa la configuració',
|
||||
'settings_save_success' => 'S\'ha desat la configuració',
|
||||
'system_version' => 'System Version',
|
||||
'categories' => 'Categories',
|
||||
|
||||
// App Settings
|
||||
'app_customization' => 'Personalització',
|
||||
@@ -25,8 +27,8 @@ return [
|
||||
'app_secure_images' => 'Pujades d\'imatges amb més seguretat',
|
||||
'app_secure_images_toggle' => 'Activa les pujades d\'imatges amb més seguretat',
|
||||
'app_secure_images_desc' => 'Per motius de rendiment, totes les imatges són públiques. Aquesta opció afegeix una cadena aleatòria i difícil d\'endevinar al davant dels URL d\'imatges. Assegureu-vos que els índexs de directoris no estiguin activats per a evitar-hi l\'accés de manera fàcil.',
|
||||
'app_editor' => 'Editor de pàgines',
|
||||
'app_editor_desc' => 'Seleccioneu quin editor faran servir tots els usuaris per a editar les pàgines.',
|
||||
'app_default_editor' => 'Default Page Editor',
|
||||
'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
|
||||
'app_custom_html' => 'Contingut personalitzat a la capçalera HTML',
|
||||
'app_custom_html_desc' => 'Aquí podeu afegir contingut que s\'inserirà a la part final de la secció <head> de cada pàgina. És útil per a sobreescriure estils o afegir-hi codi d\'analítiques.',
|
||||
'app_custom_html_disabled_notice' => 'El contingut personalitzat a la capçalera HTML es desactiva en aquesta pàgina de la configuració per a assegurar que qualsevol canvi que trenqui el web es pugui desfer.',
|
||||
@@ -150,6 +152,7 @@ return [
|
||||
'role_access_api' => 'Accedeix a l\'API del sistema',
|
||||
'role_manage_settings' => 'Gestiona la configuració de l\'aplicació',
|
||||
'role_export_content' => 'Export content',
|
||||
'role_editor_change' => 'Change page editor',
|
||||
'role_asset' => 'Permisos de recursos',
|
||||
'roles_system_warning' => 'Tingueu en compte que l\'accés a qualsevol dels tres permisos de dalt pot permetre que un usuari alteri els seus propis permisos o els privilegis d\'altres usuaris del sistema. Assigneu rols amb aquests permisos només a usuaris de confiança.',
|
||||
'role_asset_desc' => 'Aquests permisos controlen l\'accés per defecte als recursos del sistema. Els permisos de llibres, capítols i pàgines tindran més importància que aquests permisos.',
|
||||
@@ -275,6 +278,8 @@ return [
|
||||
'es' => 'Español',
|
||||
'es_AR' => 'Español Argentina',
|
||||
'et' => 'Eesti keel',
|
||||
'eu' => 'Euskara',
|
||||
'fa' => 'فارسی',
|
||||
'fr' => 'Français',
|
||||
'he' => 'עברית',
|
||||
'hr' => 'Hrvatski',
|
||||
|
||||
@@ -24,6 +24,7 @@ return [
|
||||
'width' => 'Width',
|
||||
'height' => 'Height',
|
||||
'More' => 'More',
|
||||
'select' => 'Select...',
|
||||
|
||||
// Toolbar
|
||||
'formats' => 'Formats',
|
||||
@@ -52,9 +53,10 @@ return [
|
||||
'align_left' => 'Align left',
|
||||
'align_center' => 'Align center',
|
||||
'align_right' => 'Align right',
|
||||
'align_justify' => 'Align justify',
|
||||
'align_justify' => 'Justify',
|
||||
'list_bullet' => 'Bullet list',
|
||||
'list_numbered' => 'Numbered list',
|
||||
'list_task' => 'Task list',
|
||||
'indent_increase' => 'Increase indent',
|
||||
'indent_decrease' => 'Decrease indent',
|
||||
'table' => 'Table',
|
||||
@@ -91,7 +93,10 @@ return [
|
||||
'cell_properties_title' => 'Cell Properties',
|
||||
'cell_type' => 'Cell type',
|
||||
'cell_type_cell' => 'Cell',
|
||||
'cell_scope' => 'Scope',
|
||||
'cell_type_header' => 'Header cell',
|
||||
'merge_cells' => 'Merge cells',
|
||||
'split_cell' => 'Split cell',
|
||||
'table_row_group' => 'Row Group',
|
||||
'table_column_group' => 'Column Group',
|
||||
'horizontal_align' => 'Horizontal align',
|
||||
@@ -119,6 +124,16 @@ return [
|
||||
'caption' => 'Caption',
|
||||
'show_caption' => 'Show caption',
|
||||
'constrain' => 'Constrain proportions',
|
||||
'cell_border_solid' => 'Solid',
|
||||
'cell_border_dotted' => 'Dotted',
|
||||
'cell_border_dashed' => 'Dashed',
|
||||
'cell_border_double' => 'Double',
|
||||
'cell_border_groove' => 'Groove',
|
||||
'cell_border_ridge' => 'Ridge',
|
||||
'cell_border_inset' => 'Inset',
|
||||
'cell_border_outset' => 'Outset',
|
||||
'cell_border_none' => 'None',
|
||||
'cell_border_hidden' => 'Hidden',
|
||||
|
||||
// Images, links, details/summary & embed
|
||||
'source' => 'Source',
|
||||
@@ -139,12 +154,14 @@ return [
|
||||
'toggle_label' => 'Toggle label',
|
||||
|
||||
// About view
|
||||
'about' => 'About the editor',
|
||||
'about_title' => 'About the WYSIWYG Editor',
|
||||
'editor_license' => 'Editor License & Copyright',
|
||||
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.',
|
||||
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
|
||||
'save_continue' => 'Save Page & Continue',
|
||||
'callouts_cycle' => '(Keep pressing to toggle through types)',
|
||||
'link_selector' => 'Link to content',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcut' => 'Shortcut',
|
||||
'shortcuts_intro' => 'The following shortcuts are available in the editor:',
|
||||
|
||||
@@ -196,9 +196,19 @@ return [
|
||||
'pages_edit_draft_save_at' => 'Koncept uložen v ',
|
||||
'pages_edit_delete_draft' => 'Odstranit koncept',
|
||||
'pages_edit_discard_draft' => 'Zahodit koncept',
|
||||
'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
|
||||
'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
|
||||
'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
|
||||
'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
|
||||
'pages_edit_set_changelog' => 'Nastavit protokol změn',
|
||||
'pages_edit_enter_changelog_desc' => 'Zadejte stručný popis změn, které jste provedli',
|
||||
'pages_edit_enter_changelog' => 'Zadejte protokol změn',
|
||||
'pages_editor_switch_title' => 'Switch Editor',
|
||||
'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
|
||||
'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
|
||||
'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
|
||||
'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
|
||||
'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
|
||||
'pages_save' => 'Uložit stránku',
|
||||
'pages_title' => 'Nadpis stránky',
|
||||
'pages_name' => 'Název stránky',
|
||||
@@ -225,6 +235,7 @@ return [
|
||||
'pages_revisions_number' => 'Č. ',
|
||||
'pages_revisions_numbered' => 'Revize č. :id',
|
||||
'pages_revisions_numbered_changes' => 'Změny revize č. :id',
|
||||
'pages_revisions_editor' => 'Editor Type',
|
||||
'pages_revisions_changelog' => 'Protokol změn',
|
||||
'pages_revisions_changes' => 'Změny',
|
||||
'pages_revisions_current' => 'Aktuální verze',
|
||||
|
||||
@@ -10,6 +10,8 @@ return [
|
||||
'settings' => 'Nastavení',
|
||||
'settings_save' => 'Uložit nastavení',
|
||||
'settings_save_success' => 'Nastavení uloženo',
|
||||
'system_version' => 'System Version',
|
||||
'categories' => 'Categories',
|
||||
|
||||
// App Settings
|
||||
'app_customization' => 'Přizpůsobení',
|
||||
@@ -25,8 +27,8 @@ return [
|
||||
'app_secure_images' => 'Nahrávat obrázky neveřejně a zabezpečeně',
|
||||
'app_secure_images_toggle' => 'Zapnout bezpečnější nahrávání obrázků',
|
||||
'app_secure_images_desc' => 'Z výkonnostních důvodů jsou všechny obrázky veřejně dostupné. Tato volba přidá do adresy obrázku náhodný řetězec, aby nikdo neodhadnul adresu obrázku. Ujistěte se, že server nezobrazuje v adresáři seznam souborů, což by přístup k přístup opět otevřelo.',
|
||||
'app_editor' => 'Editor stránek',
|
||||
'app_editor_desc' => 'Zvolte který editor budou užívat všichni uživatelé k úpravě stránek.',
|
||||
'app_default_editor' => 'Default Page Editor',
|
||||
'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
|
||||
'app_custom_html' => 'Vlastní obsah hlavičky HTML',
|
||||
'app_custom_html_desc' => 'Cokoliv sem napíšete bude přidáno na konec sekce <head> v každém místě této aplikace. To se hodí pro přidávání nebo změnu CSS stylů nebo přidání kódu pro analýzu používání (např.: google analytics.).',
|
||||
'app_custom_html_disabled_notice' => 'Na této stránce nastavení je zakázán vlastní obsah HTML hlavičky, aby bylo zajištěno, že bude možné vrátit případnou problematickou úpravu.',
|
||||
@@ -150,6 +152,7 @@ return [
|
||||
'role_access_api' => 'Přístup k systémovému API',
|
||||
'role_manage_settings' => 'Správa nastavení aplikace',
|
||||
'role_export_content' => 'Exportovat obsah',
|
||||
'role_editor_change' => 'Change page editor',
|
||||
'role_asset' => 'Obsahová oprávnění',
|
||||
'roles_system_warning' => 'Berte na vědomí, že přístup k některému ze tří výše uvedených oprávnění může uživateli umožnit změnit svá vlastní oprávnění nebo oprávnění ostatních uživatelů v systému. Přiřazujte role s těmito oprávněními pouze důvěryhodným uživatelům.',
|
||||
'role_asset_desc' => 'Tato oprávnění řídí přístup k obsahu napříč systémem. Specifická oprávnění na knihách, kapitolách a stránkách převáží tato nastavení.',
|
||||
@@ -275,6 +278,8 @@ return [
|
||||
'es' => 'Español',
|
||||
'es_AR' => 'Español Argentina',
|
||||
'et' => 'Eesti keel',
|
||||
'eu' => 'Euskara',
|
||||
'fa' => 'فارسی',
|
||||
'fr' => 'Français',
|
||||
'he' => 'עברית',
|
||||
'hr' => 'Hrvatski',
|
||||
|
||||
@@ -24,6 +24,7 @@ return [
|
||||
'width' => 'Width',
|
||||
'height' => 'Height',
|
||||
'More' => 'More',
|
||||
'select' => 'Select...',
|
||||
|
||||
// Toolbar
|
||||
'formats' => 'Formats',
|
||||
@@ -52,9 +53,10 @@ return [
|
||||
'align_left' => 'Align left',
|
||||
'align_center' => 'Align center',
|
||||
'align_right' => 'Align right',
|
||||
'align_justify' => 'Align justify',
|
||||
'align_justify' => 'Justify',
|
||||
'list_bullet' => 'Bullet list',
|
||||
'list_numbered' => 'Numbered list',
|
||||
'list_task' => 'Task list',
|
||||
'indent_increase' => 'Increase indent',
|
||||
'indent_decrease' => 'Decrease indent',
|
||||
'table' => 'Table',
|
||||
@@ -91,7 +93,10 @@ return [
|
||||
'cell_properties_title' => 'Cell Properties',
|
||||
'cell_type' => 'Cell type',
|
||||
'cell_type_cell' => 'Cell',
|
||||
'cell_scope' => 'Scope',
|
||||
'cell_type_header' => 'Header cell',
|
||||
'merge_cells' => 'Merge cells',
|
||||
'split_cell' => 'Split cell',
|
||||
'table_row_group' => 'Row Group',
|
||||
'table_column_group' => 'Column Group',
|
||||
'horizontal_align' => 'Horizontal align',
|
||||
@@ -119,6 +124,16 @@ return [
|
||||
'caption' => 'Caption',
|
||||
'show_caption' => 'Show caption',
|
||||
'constrain' => 'Constrain proportions',
|
||||
'cell_border_solid' => 'Solid',
|
||||
'cell_border_dotted' => 'Dotted',
|
||||
'cell_border_dashed' => 'Dashed',
|
||||
'cell_border_double' => 'Double',
|
||||
'cell_border_groove' => 'Groove',
|
||||
'cell_border_ridge' => 'Ridge',
|
||||
'cell_border_inset' => 'Inset',
|
||||
'cell_border_outset' => 'Outset',
|
||||
'cell_border_none' => 'None',
|
||||
'cell_border_hidden' => 'Hidden',
|
||||
|
||||
// Images, links, details/summary & embed
|
||||
'source' => 'Source',
|
||||
@@ -139,12 +154,14 @@ return [
|
||||
'toggle_label' => 'Toggle label',
|
||||
|
||||
// About view
|
||||
'about' => 'About the editor',
|
||||
'about_title' => 'About the WYSIWYG Editor',
|
||||
'editor_license' => 'Editor License & Copyright',
|
||||
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.',
|
||||
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
|
||||
'save_continue' => 'Save Page & Continue',
|
||||
'callouts_cycle' => '(Keep pressing to toggle through types)',
|
||||
'link_selector' => 'Link to content',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
'shortcut' => 'Shortcut',
|
||||
'shortcuts_intro' => 'The following shortcuts are available in the editor:',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user