Compare commits

..

1 Commits

Author SHA1 Message Date
Dan Brown
a9f5e98ba9 Drawings: Added class to extract drawio data from png files 2025-06-19 17:23:56 +01:00
401 changed files with 3496 additions and 7611 deletions

View File

@@ -438,7 +438,7 @@ javadataherian :: Persian
Ludo-code :: French
hollsten :: Swedish
Ngoc Lan Phung (lanpncz) :: Vietnamese
Worive :: Catalan; French
Worive :: Catalan
Илья Скаба (skabailya) :: Russian
Irjan Olsen (Irch) :: Norwegian Bokmal
Aleksandar Jovanovic (jovanoviczaleksandar) :: Serbian (Cyrillic)
@@ -489,19 +489,3 @@ Hari (muhhari) :: Indonesian
仙君御 (xjy) :: Chinese Simplified
TapioM :: Finnish
lingb58 :: Chinese Traditional
Angel Pandey (angel-pandey) :: Nepali
Supriya Shrestha (supriyashrestha) :: Nepali
gprabhat :: Nepali
CellCat :: Chinese Simplified
Al Desrahim (aldesrahim) :: Indonesian
ahmad abbaspour (deshneh.dar.diss) :: Persian
Erjon K. (ekr) :: Albanian
LiZerui (iamzrli) :: Chinese Traditional
Ticker (ticker.com) :: Hebrew
CrazyComputer :: Chinese Simplified
Firr (FirrV) :: Russian
João Faro (FaroJoaoFaro) :: Portuguese
Danilo dos Santos Barbosa (bozochegou) :: Portuguese, Brazilian
Chris (furesoft) :: German
Silvia Isern (eiendragon) :: Catalan
Dennis Kron Pedersen (ahjdp) :: Danish

6
.gitignore vendored
View File

@@ -8,10 +8,10 @@ Homestead.yaml
.idea
npm-debug.log
yarn-error.log
/public/dist/*.map
/public/dist
/public/plugins
/public/css/*.map
/public/js/*.map
/public/css
/public/js
/public/bower
/public/build/
/public/favicon.ico

View File

@@ -57,13 +57,16 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
*
* @throws LdapException
* @throws LdapException*@throws \BookStack\Exceptions\JsonDebugException
* @throws LoginAttemptException
* @throws JsonDebugException
*
* @return bool
*/
public function attempt(array $credentials = [], $remember = false): bool
public function attempt(array $credentials = [], $remember = false)
{
$username = $credentials['username'];
$userDetails = $this->ldapService->getUserDetails($username);

View File

@@ -11,7 +11,6 @@ use BookStack\Entities\Tools\MixedEntityListLoader;
use BookStack\Permissions\PermissionApplicator;
use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\Relation;
class ActivityQueries
@@ -68,7 +67,6 @@ class ActivityQueries
$activity = $query->orderBy('created_at', 'desc')
->with(['loggable' => function (Relation $query) {
/** @var MorphTo<Entity, Activity> $query */
$query->withTrashed();
}, 'user.avatar'])
->skip($count * ($page - 1))

View File

@@ -12,8 +12,6 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
* @property int $id
* @property string $name
* @property string $value
* @property int $entity_id
* @property string $entity_type
* @property int $order
*/
class Tag extends Model

View File

@@ -20,8 +20,7 @@ class PageUpdateNotificationHandler extends BaseNotificationHandler
throw new \InvalidArgumentException("Detail for page update notifications must be a page");
}
// Get the last update from activity
/** @var ?Activity $lastUpdate */
// Get last update from activity
$lastUpdate = $detail->activity()
->where('type', '=', ActivityType::PAGE_UPDATE)
->where('id', '!=', $activity->id)

View File

@@ -3,15 +3,17 @@
namespace BookStack\Activity\Tools;
use BookStack\Activity\Models\Tag;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
class TagClassGenerator
{
public function __construct(
protected Entity $entity
) {
protected array $tags;
/**
* @param Tag[] $tags
*/
public function __construct(array $tags)
{
$this->tags = $tags;
}
/**
@@ -20,23 +22,14 @@ class TagClassGenerator
public function generate(): array
{
$classes = [];
$tags = $this->entity->tags->all();
foreach ($tags as $tag) {
array_push($classes, ...$this->generateClassesForTag($tag));
}
if ($this->entity instanceof BookChild && userCan('view', $this->entity->book)) {
$bookTags = $this->entity->book->tags;
foreach ($bookTags as $bookTag) {
array_push($classes, ...$this->generateClassesForTag($bookTag, 'book-'));
}
}
if ($this->entity instanceof Page && $this->entity->chapter && userCan('view', $this->entity->chapter)) {
$chapterTags = $this->entity->chapter->tags;
foreach ($chapterTags as $chapterTag) {
array_push($classes, ...$this->generateClassesForTag($chapterTag, 'chapter-'));
foreach ($this->tags as $tag) {
$name = $this->normalizeTagClassString($tag->name);
$value = $this->normalizeTagClassString($tag->value);
$classes[] = 'tag-name-' . $name;
if ($value) {
$classes[] = 'tag-value-' . $value;
$classes[] = 'tag-pair-' . $name . '-' . $value;
}
}
@@ -48,22 +41,6 @@ class TagClassGenerator
return implode(' ', $this->generate());
}
/**
* @return string[]
*/
protected function generateClassesForTag(Tag $tag, string $prefix = ''): array
{
$classes = [];
$name = $this->normalizeTagClassString($tag->name);
$value = $this->normalizeTagClassString($tag->value);
$classes[] = "{$prefix}tag-name-{$name}";
if ($value) {
$classes[] = "{$prefix}tag-value-{$value}";
$classes[] = "{$prefix}tag-pair-{$name}-{$value}";
}
return $classes;
}
protected function normalizeTagClassString(string $value): string
{
$value = str_replace(' ', '', strtolower($value));

View File

@@ -50,7 +50,7 @@ class WebhookFormatter
}
if ($this->detail instanceof Model) {
$data['related_item'] = $this->formatModel($this->detail);
$data['related_item'] = $this->formatModel();
}
return $data;
@@ -83,8 +83,10 @@ class WebhookFormatter
);
}
protected function formatModel(Model $model): array
protected function formatModel(): array
{
/** @var Model $model */
$model = $this->detail;
$model->unsetRelations();
foreach ($this->modelFormatters as $formatter) {

View File

@@ -11,7 +11,6 @@
// Configured mail encryption method.
// STARTTLS should still be attempted, but tls/ssl forces TLS usage.
$mailEncryption = env('MAIL_ENCRYPTION', null);
$mailPort = intval(env('MAIL_PORT', 587));
return [
@@ -34,13 +33,13 @@ return [
'transport' => 'smtp',
'scheme' => null,
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
'port' => $mailPort,
'port' => env('MAIL_PORT', 587),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'verify_peer' => env('MAIL_VERIFY_SSL', true),
'timeout' => null,
'local_domain' => null,
'require_tls' => ($mailEncryption === 'tls' || $mailEncryption === 'ssl' || $mailPort === 465),
'tls_required' => ($mailEncryption === 'tls' || $mailEncryption === 'ssl'),
],
'sendmail' => [

View File

@@ -18,7 +18,6 @@ use BookStack\Exceptions\NotFoundException;
use BookStack\Facades\Activity;
use BookStack\Http\Controller;
use BookStack\References\ReferenceFetcher;
use BookStack\Util\DatabaseTransaction;
use BookStack\Util\SimpleListOptions;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
@@ -264,9 +263,7 @@ class BookController extends Controller
$this->checkPermission('bookshelf-create-all');
$this->checkPermission('book-create-all');
$shelf = (new DatabaseTransaction(function () use ($book, $transformer) {
return $transformer->transformBookToShelf($book);
}))->run();
$shelf = $transformer->transformBookToShelf($book);
return redirect($shelf->getUrl());
}

View File

@@ -2,7 +2,6 @@
namespace BookStack\Entities\Controllers;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Queries\ChapterQueries;
use BookStack\Entities\Queries\EntityQueries;
@@ -10,11 +9,12 @@ use BookStack\Entities\Repos\ChapterRepo;
use BookStack\Exceptions\PermissionsException;
use BookStack\Http\ApiController;
use Exception;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Http\Request;
class ChapterApiController extends ApiController
{
protected array $rules = [
protected $rules = [
'create' => [
'book_id' => ['required', 'integer'],
'name' => ['required', 'string', 'max:255'],
@@ -144,10 +144,7 @@ class ChapterApiController extends ApiController
$chapter->load(['tags']);
$chapter->makeVisible('description_html');
$chapter->setAttribute('description_html', $chapter->descriptionHtml());
/** @var Book $book */
$book = $chapter->book()->first();
$chapter->setAttribute('book_slug', $book->slug);
$chapter->setAttribute('book_slug', $chapter->book()->first()->slug);
return $chapter;
}

View File

@@ -18,7 +18,6 @@ use BookStack\Exceptions\NotifyException;
use BookStack\Exceptions\PermissionsException;
use BookStack\Http\Controller;
use BookStack\References\ReferenceFetcher;
use BookStack\Util\DatabaseTransaction;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Throwable;
@@ -270,9 +269,7 @@ class ChapterController extends Controller
$this->checkOwnablePermission('chapter-delete', $chapter);
$this->checkPermission('book-create-all');
$book = (new DatabaseTransaction(function () use ($chapter, $transformer) {
return $transformer->transformChapterToBook($chapter);
}))->run();
$book = $transformer->transformChapterToBook($chapter);
return redirect($book->getUrl());
}

View File

@@ -12,7 +12,7 @@ use Illuminate\Http\Request;
class PageApiController extends ApiController
{
protected array $rules = [
protected $rules = [
'create' => [
'book_id' => ['required_without:chapter_id', 'integer'],
'chapter_id' => ['required_without:book_id', 'integer'],

View File

@@ -26,7 +26,6 @@ use BookStack\Users\Models\HasOwner;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -284,14 +283,10 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
public function getParent(): ?self
{
if ($this instanceof Page) {
/** @var BelongsTo<Chapter|Book, Page> $builder */
$builder = $this->chapter_id ? $this->chapter() : $this->book();
return $builder->withTrashed()->first();
return $this->chapter_id ? $this->chapter()->withTrashed()->first() : $this->book()->withTrashed()->first();
}
if ($this instanceof Chapter) {
/** @var BelongsTo<Book, Page> $builder */
$builder = $this->book();
return $builder->withTrashed()->first();
return $this->book()->withTrashed()->first();
}
return null;
@@ -300,7 +295,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
/**
* Rebuild the permissions for this entity.
*/
public function rebuildPermissions(): void
public function rebuildPermissions()
{
app()->make(JointPermissionBuilder::class)->rebuildForEntity(clone $this);
}
@@ -308,7 +303,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
/**
* Index the current entity for search.
*/
public function indexForSearch(): void
public function indexForSearch()
{
app()->make(SearchIndex::class)->indexEntity(clone $this);
}

View File

@@ -77,6 +77,7 @@ class BaseRepo
$entity->touch();
}
$entity->rebuildPermissions();
$entity->indexForSearch();
$this->referenceStore->updateForEntity($entity);
@@ -138,7 +139,7 @@ class BaseRepo
/**
* Sort the parent of the given entity, if any auto sort actions are set for it.
* Typically ran during create/update/insert events.
* Typical ran during create/update/insert events.
*/
public function sortParent(Entity $entity): void
{

View File

@@ -10,7 +10,6 @@ use BookStack\Exceptions\ImageUploadException;
use BookStack\Facades\Activity;
use BookStack\Sorting\SortRule;
use BookStack\Uploads\ImageRepo;
use BookStack\Util\DatabaseTransaction;
use Exception;
use Illuminate\Http\UploadedFile;
@@ -29,22 +28,19 @@ class BookRepo
*/
public function create(array $input): Book
{
return (new DatabaseTransaction(function () use ($input) {
$book = new Book();
$book = new Book();
$this->baseRepo->create($book, $input);
$this->baseRepo->updateCoverImage($book, $input['image'] ?? null);
$this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'] ?? null));
Activity::add(ActivityType::BOOK_CREATE, $book);
$this->baseRepo->create($book, $input);
$this->baseRepo->updateCoverImage($book, $input['image'] ?? null);
$this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'] ?? null));
Activity::add(ActivityType::BOOK_CREATE, $book);
$defaultBookSortSetting = intval(setting('sorting-book-default', '0'));
if ($defaultBookSortSetting && SortRule::query()->find($defaultBookSortSetting)) {
$book->sort_rule_id = $defaultBookSortSetting;
$book->save();
}
$defaultBookSortSetting = intval(setting('sorting-book-default', '0'));
if ($defaultBookSortSetting && SortRule::query()->find($defaultBookSortSetting)) {
$book->sort_rule_id = $defaultBookSortSetting;
$book->save();
}
return $book;
}))->run();
return $book;
}
/**

View File

@@ -7,7 +7,6 @@ use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Tools\TrashCan;
use BookStack\Facades\Activity;
use BookStack\Util\DatabaseTransaction;
use Exception;
class BookshelfRepo
@@ -24,14 +23,13 @@ class BookshelfRepo
*/
public function create(array $input, array $bookIds): Bookshelf
{
return (new DatabaseTransaction(function () use ($input, $bookIds) {
$shelf = new Bookshelf();
$this->baseRepo->create($shelf, $input);
$this->baseRepo->updateCoverImage($shelf, $input['image'] ?? null);
$this->updateBooks($shelf, $bookIds);
Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf);
return $shelf;
}))->run();
$shelf = new Bookshelf();
$this->baseRepo->create($shelf, $input);
$this->baseRepo->updateCoverImage($shelf, $input['image'] ?? null);
$this->updateBooks($shelf, $bookIds);
Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf);
return $shelf;
}
/**
@@ -56,37 +54,20 @@ class BookshelfRepo
/**
* Update which books are assigned to this shelf by syncing the given book ids.
* Function ensures the managed books are visible to the current user and existing,
* and that the user does not alter the assignment of books that are not visible to them.
* Function ensures the books are visible to the current user and existing.
*/
protected function updateBooks(Bookshelf $shelf, array $bookIds): void
protected function updateBooks(Bookshelf $shelf, array $bookIds)
{
$numericIDs = collect($bookIds)->map(function ($id) {
return intval($id);
});
$existingBookIds = $shelf->books()->pluck('id')->toArray();
$visibleExistingBookIds = $this->bookQueries->visibleForList()
->whereIn('id', $existingBookIds)
->pluck('id')
->toArray();
$nonVisibleExistingBookIds = array_values(array_diff($existingBookIds, $visibleExistingBookIds));
$newIdsToAssign = $this->bookQueries->visibleForList()
$syncData = $this->bookQueries->visibleForList()
->whereIn('id', $bookIds)
->pluck('id')
->toArray();
$maxNewIndex = max($numericIDs->keys()->toArray() ?: [0]);
$syncData = [];
foreach ($newIdsToAssign as $id) {
$syncData[$id] = ['order' => $numericIDs->search($id)];
}
foreach ($nonVisibleExistingBookIds as $index => $id) {
$syncData[$id] = ['order' => $maxNewIndex + ($index + 1)];
}
->mapWithKeys(function ($bookId) use ($numericIDs) {
return [$bookId => ['order' => $numericIDs->search($bookId)]];
});
$shelf->books()->sync($syncData);
}

View File

@@ -11,7 +11,6 @@ use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\MoveOperationException;
use BookStack\Exceptions\PermissionsException;
use BookStack\Facades\Activity;
use BookStack\Util\DatabaseTransaction;
use Exception;
class ChapterRepo
@@ -28,18 +27,16 @@ class ChapterRepo
*/
public function create(array $input, Book $parentBook): Chapter
{
return (new DatabaseTransaction(function () use ($input, $parentBook) {
$chapter = new Chapter();
$chapter->book_id = $parentBook->id;
$chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
$this->baseRepo->create($chapter, $input);
$this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
Activity::add(ActivityType::CHAPTER_CREATE, $chapter);
$chapter = new Chapter();
$chapter->book_id = $parentBook->id;
$chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1;
$this->baseRepo->create($chapter, $input);
$this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
Activity::add(ActivityType::CHAPTER_CREATE, $chapter);
$this->baseRepo->sortParent($chapter);
$this->baseRepo->sortParent($chapter);
return $chapter;
}))->run();
return $chapter;
}
/**
@@ -91,14 +88,12 @@ class ChapterRepo
throw new PermissionsException('User does not have permission to create a chapter within the chosen book');
}
return (new DatabaseTransaction(function () use ($chapter, $parent) {
$chapter->changeBook($parent->id);
$chapter->rebuildPermissions();
Activity::add(ActivityType::CHAPTER_MOVE, $chapter);
$chapter->changeBook($parent->id);
$chapter->rebuildPermissions();
Activity::add(ActivityType::CHAPTER_MOVE, $chapter);
$this->baseRepo->sortParent($chapter);
$this->baseRepo->sortParent($chapter);
return $parent;
}))->run();
return $parent;
}
}

View File

@@ -18,7 +18,6 @@ use BookStack\Exceptions\PermissionsException;
use BookStack\Facades\Activity;
use BookStack\References\ReferenceStore;
use BookStack\References\ReferenceUpdater;
use BookStack\Util\DatabaseTransaction;
use Exception;
class PageRepo
@@ -62,10 +61,8 @@ class PageRepo
]);
}
(new DatabaseTransaction(function () use ($page) {
$page->save();
$page->refresh()->rebuildPermissions();
}))->run();
$page->save();
$page->refresh()->rebuildPermissions();
return $page;
}
@@ -75,29 +72,26 @@ class PageRepo
*/
public function publishDraft(Page $draft, array $input): Page
{
return (new DatabaseTransaction(function () use ($draft, $input) {
$draft->draft = false;
$draft->revision_count = 1;
$draft->priority = $this->getNewPriority($draft);
$this->updateTemplateStatusAndContentFromInput($draft, $input);
$this->baseRepo->update($draft, $input);
$draft->rebuildPermissions();
$draft->draft = false;
$draft->revision_count = 1;
$draft->priority = $this->getNewPriority($draft);
$this->updateTemplateStatusAndContentFromInput($draft, $input);
$this->baseRepo->update($draft, $input);
$summary = trim($input['summary'] ?? '') ?: trans('entities.pages_initial_revision');
$this->revisionRepo->storeNewForPage($draft, $summary);
$draft->refresh();
$summary = trim($input['summary'] ?? '') ?: trans('entities.pages_initial_revision');
$this->revisionRepo->storeNewForPage($draft, $summary);
$draft->refresh();
Activity::add(ActivityType::PAGE_CREATE, $draft);
$this->baseRepo->sortParent($draft);
Activity::add(ActivityType::PAGE_CREATE, $draft);
$this->baseRepo->sortParent($draft);
return $draft;
}))->run();
return $draft;
}
/**
* Directly update the content for the given page from the provided input.
* Used for direct content access in a way that performs required changes
* (Search index and reference regen) without performing an official update.
* (Search index & reference regen) without performing an official update.
*/
public function setContentFromInput(Page $page, array $input): void
{
@@ -122,7 +116,7 @@ class PageRepo
$page->revision_count++;
$page->save();
// Remove all update drafts for this user and page.
// Remove all update drafts for this user & page.
$this->revisionRepo->deleteDraftsForCurrentUser($page);
// Save a revision after updating
@@ -275,18 +269,16 @@ class PageRepo
throw new PermissionsException('User does not have permission to create a page within the new parent');
}
return (new DatabaseTransaction(function () use ($page, $parent) {
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null;
$newBookId = ($parent instanceof Chapter) ? $parent->book->id : $parent->id;
$page->changeBook($newBookId);
$page->rebuildPermissions();
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null;
$newBookId = ($parent instanceof Chapter) ? $parent->book->id : $parent->id;
$page->changeBook($newBookId);
$page->rebuildPermissions();
Activity::add(ActivityType::PAGE_MOVE, $page);
Activity::add(ActivityType::PAGE_MOVE, $page);
$this->baseRepo->sortParent($page);
$this->baseRepo->sortParent($page);
return $parent;
}))->run();
return $parent;
}
/**

View File

@@ -13,12 +13,17 @@ use BookStack\Facades\Activity;
class HierarchyTransformer
{
public function __construct(
protected BookRepo $bookRepo,
protected BookshelfRepo $shelfRepo,
protected Cloner $cloner,
protected TrashCan $trashCan
) {
protected BookRepo $bookRepo;
protected BookshelfRepo $shelfRepo;
protected Cloner $cloner;
protected TrashCan $trashCan;
public function __construct(BookRepo $bookRepo, BookshelfRepo $shelfRepo, Cloner $cloner, TrashCan $trashCan)
{
$this->bookRepo = $bookRepo;
$this->shelfRepo = $shelfRepo;
$this->cloner = $cloner;
$this->trashCan = $trashCan;
}
/**

View File

@@ -15,7 +15,6 @@ use BookStack\Exceptions\NotifyException;
use BookStack\Facades\Activity;
use BookStack\Uploads\AttachmentService;
use BookStack\Uploads\ImageService;
use BookStack\Util\DatabaseTransaction;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
@@ -358,26 +357,25 @@ class TrashCan
/**
* Destroy the given entity.
* Returns the number of total entities destroyed in the operation.
*
* @throws Exception
*/
public function destroyEntity(Entity $entity): int
{
$result = (new DatabaseTransaction(function () use ($entity) {
if ($entity instanceof Page) {
return $this->destroyPage($entity);
} else if ($entity instanceof Chapter) {
return $this->destroyChapter($entity);
} else if ($entity instanceof Book) {
return $this->destroyBook($entity);
} else if ($entity instanceof Bookshelf) {
return $this->destroyShelf($entity);
}
return null;
}))->run();
if ($entity instanceof Page) {
return $this->destroyPage($entity);
}
if ($entity instanceof Chapter) {
return $this->destroyChapter($entity);
}
if ($entity instanceof Book) {
return $this->destroyBook($entity);
}
if ($entity instanceof Bookshelf) {
return $this->destroyShelf($entity);
}
return $result ?? 0;
return 0;
}
/**

View File

@@ -0,0 +1,7 @@
<?php
namespace BookStack\Exceptions;
class DrawioPngReaderException extends \Exception
{
}

View File

@@ -4,7 +4,6 @@ namespace BookStack\Exports\Controllers;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Exports\ExportFormatter;
use BookStack\Exports\ZipExports\ZipExportBuilder;
use BookStack\Http\ApiController;
use Throwable;
@@ -64,15 +63,4 @@ class BookExportApiController extends ApiController
return $this->download()->directly($markdown, $book->slug . '.md');
}
/**
* Export a book as a contained ZIP export file.
*/
public function exportZip(int $id, ZipExportBuilder $builder)
{
$book = $this->queries->findVisibleByIdOrFail($id);
$zip = $builder->buildForBook($book);
return $this->download()->streamedFileDirectly($zip, $book->slug . '.zip', true);
}
}

View File

@@ -4,7 +4,6 @@ namespace BookStack\Exports\Controllers;
use BookStack\Entities\Queries\ChapterQueries;
use BookStack\Exports\ExportFormatter;
use BookStack\Exports\ZipExports\ZipExportBuilder;
use BookStack\Http\ApiController;
use Throwable;
@@ -64,15 +63,4 @@ class ChapterExportApiController extends ApiController
return $this->download()->directly($markdown, $chapter->slug . '.md');
}
/**
* Export a chapter as a contained ZIP file.
*/
public function exportZip(int $id, ZipExportBuilder $builder)
{
$chapter = $this->queries->findVisibleByIdOrFail($id);
$zip = $builder->buildForChapter($chapter);
return $this->download()->streamedFileDirectly($zip, $chapter->slug . '.zip', true);
}
}

View File

@@ -1,144 +0,0 @@
<?php
declare(strict_types=1);
namespace BookStack\Exports\Controllers;
use BookStack\Exceptions\ZipImportException;
use BookStack\Exceptions\ZipValidationException;
use BookStack\Exports\ImportRepo;
use BookStack\Http\ApiController;
use BookStack\Uploads\AttachmentService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
class ImportApiController extends ApiController
{
public function __construct(
protected ImportRepo $imports,
) {
$this->middleware('can:content-import');
}
/**
* List existing ZIP imports visible to the user.
* Requires permission to import content.
*/
public function list(): JsonResponse
{
$query = $this->imports->queryVisible();
return $this->apiListingResponse($query, [
'id', 'name', 'size', 'type', 'created_by', 'created_at', 'updated_at'
]);
}
/**
* Start a new import from a ZIP file.
* This does not actually run the import since that is performed via the "run" endpoint.
* This uploads, validates and stores the ZIP file so it's ready to be imported.
*
* This "file" parameter must be a BookStack-compatible ZIP file, and this must be
* sent via a 'multipart/form-data' type request.
*
* Requires permission to import content.
*/
public function create(Request $request): JsonResponse
{
$this->validate($request, $this->rules()['create']);
$file = $request->file('file');
try {
$import = $this->imports->storeFromUpload($file);
} catch (ZipValidationException $exception) {
$message = "ZIP upload failed with the following validation errors: \n" . $this->formatErrors($exception->errors);
return $this->jsonError($message, 422);
}
return response()->json($import);
}
/**
* Read details of a pending ZIP import.
* The "details" property contains high-level metadata regarding the ZIP import content,
* and the structure of this will change depending on import "type".
* Requires permission to import content.
*/
public function read(int $id): JsonResponse
{
$import = $this->imports->findVisible($id);
$import->setAttribute('details', $import->decodeMetadata());
return response()->json($import);
}
/**
* Run the import process for an uploaded ZIP import.
* The "parent_id" and "parent_type" parameters are required when the import type is "chapter" or "page".
* On success, this endpoint returns the imported item.
* Requires permission to import content.
*/
public function run(int $id, Request $request): JsonResponse
{
$import = $this->imports->findVisible($id);
$parent = null;
$rules = $this->rules()['run'];
if ($import->type === 'page' || $import->type === 'chapter') {
$rules['parent_type'][] = 'required';
$rules['parent_id'][] = 'required';
$data = $this->validate($request, $rules);
$parent = "{$data['parent_type']}:{$data['parent_id']}";
}
try {
$entity = $this->imports->runImport($import, $parent);
} catch (ZipImportException $exception) {
$message = "ZIP import failed with the following errors: \n" . $this->formatErrors($exception->errors);
return $this->jsonError($message);
}
return response()->json($entity->withoutRelations());
}
/**
* Delete a pending ZIP import from the system.
* Requires permission to import content.
*/
public function delete(int $id): Response
{
$import = $this->imports->findVisible($id);
$this->imports->deleteImport($import);
return response('', 204);
}
protected function rules(): array
{
return [
'create' => [
'file' => ['required', ...AttachmentService::getFileValidationRules()],
],
'run' => [
'parent_type' => ['string', 'in:book,chapter'],
'parent_id' => ['int'],
],
];
}
protected function formatErrors(array $errors): string
{
$parts = [];
foreach ($errors as $key => $error) {
if (is_string($key)) {
$parts[] = "[{$key}] {$error}";
} else {
$parts[] = $error;
}
}
return implode("\n", $parts);
}
}

View File

@@ -89,7 +89,7 @@ class ImportController extends Controller
try {
$entity = $this->imports->runImport($import, $parent);
} catch (ZipImportException $exception) {
session()->forget(['success', 'warning']);
session()->flush();
$this->showErrorNotification(trans('errors.import_zip_failed_notification'));
return redirect($import->getUrl())->with('import_errors', $exception->errors);
}

View File

@@ -4,7 +4,6 @@ namespace BookStack\Exports\Controllers;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Exports\ExportFormatter;
use BookStack\Exports\ZipExports\ZipExportBuilder;
use BookStack\Http\ApiController;
use Throwable;
@@ -64,15 +63,4 @@ class PageExportApiController extends ApiController
return $this->download()->directly($markdown, $page->slug . '.md');
}
/**
* Export a page as a contained ZIP file.
*/
public function exportZip(int $id, ZipExportBuilder $builder)
{
$page = $this->queries->findVisibleByIdOrFail($id);
$zip = $builder->buildForPage($page);
return $this->download()->streamedFileDirectly($zip, $page->slug . '.zip', true);
}
}

View File

@@ -28,8 +28,6 @@ class Import extends Model implements Loggable
{
use HasFactory;
protected $hidden = ['metadata'];
public function getSizeString(): string
{
$mb = round($this->size / 1000000, 2);

View File

@@ -17,7 +17,6 @@ use BookStack\Exports\ZipExports\ZipExportValidator;
use BookStack\Exports\ZipExports\ZipImportRunner;
use BookStack\Facades\Activity;
use BookStack\Uploads\FileStorage;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\File\UploadedFile;
@@ -35,11 +34,6 @@ class ImportRepo
* @return Collection<Import>
*/
public function getVisibleImports(): Collection
{
return $this->queryVisible()->get();
}
public function queryVisible(): Builder
{
$query = Import::query();
@@ -47,7 +41,7 @@ class ImportRepo
$query->where('created_by', user()->id);
}
return $query;
return $query->get();
}
public function findVisible(int $id): Import

View File

@@ -76,7 +76,7 @@ class ZipExportBuilder
$zipFile = tempnam(sys_get_temp_dir(), 'bszip-');
$zip = new ZipArchive();
$opened = $zip->open($zipFile, ZipArchive::OVERWRITE);
$opened = $zip->open($zipFile, ZipArchive::CREATE);
if ($opened !== true) {
throw new ZipExportException('Failed to create zip file for export.');
}

View File

@@ -17,17 +17,17 @@ use BookStack\Uploads\Image;
class ZipExportReferences
{
/** @var array<int, ZipExportPage> */
/** @var ZipExportPage[] */
protected array $pages = [];
/** @var array<int, ZipExportChapter> */
/** @var ZipExportChapter[] */
protected array $chapters = [];
/** @var array<int, ZipExportBook> */
/** @var ZipExportBook[] */
protected array $books = [];
/** @var array<int, ZipExportAttachment> */
/** @var ZipExportAttachment[] */
protected array $attachments = [];
/** @var array<int, ZipExportImage> */
/** @var ZipExportImage[] */
protected array $images = [];
public function __construct(
@@ -134,12 +134,11 @@ class ZipExportReferences
// Find and include images if in visibility
$page = $model->getPage();
$pageExportModel = $this->pages[$page->id] ?? ($exportModel instanceof ZipExportPage ? $exportModel : null);
if (isset($this->images[$model->id]) || ($page && $pageExportModel && userCan('view', $page))) {
if ($page && userCan('view', $page)) {
if (!isset($this->images[$model->id])) {
$exportImage = ZipExportImage::fromModel($model, $files);
$this->images[$model->id] = $exportImage;
$pageExportModel->images[] = $exportImage;
$exportModel->images[] = $exportImage;
}
return "[[bsexport:image:{$model->id}]]";
}

View File

@@ -29,10 +29,7 @@ class ZipImportReferences
/** @var Image[] */
protected array $images = [];
/**
* Mapping keyed by "type:old-reference-id" with values being the new imported equivalent model.
* @var array<string, Model>
*/
/** @var array<string, Model> */
protected array $referenceMap = [];
/** @var array<int, ZipExportPage> */
@@ -111,22 +108,6 @@ class ZipImportReferences
return null;
}
protected function replaceDrawingIdReferences(string $content): string
{
$referenceRegex = '/\sdrawio-diagram=[\'"](\d+)[\'"]/';
$result = preg_replace_callback($referenceRegex, function ($matches) {
$key = 'image:' . $matches[1];
$model = $this->referenceMap[$key] ?? null;
if ($model instanceof Image && $model->type === 'drawio') {
return ' drawio-diagram="' . $model->id . '"';
}
return $matches[0];
}, $content);
return $result ?: $content;
}
public function replaceReferences(): void
{
foreach ($this->books as $book) {
@@ -153,9 +134,7 @@ class ZipImportReferences
$exportPage = $this->zipExportPageMap[$page->id];
$contentType = $exportPage->markdown ? 'markdown' : 'html';
$content = $exportPage->markdown ?: ($exportPage->html ?: '');
$parsed = $this->parser->parseReferences($content, $this->handleReference(...));
$parsed = $this->replaceDrawingIdReferences($parsed);
$this->pageRepo->setContentFromInput($page, [
$contentType => $parsed,

View File

@@ -8,7 +8,7 @@ use Illuminate\Http\JsonResponse;
abstract class ApiController extends Controller
{
protected array $rules = [];
protected $rules = [];
/**
* Provide a paginated listing JSON response in a standard format

View File

@@ -16,7 +16,7 @@ class ContentPermissionApiController extends ApiController
) {
}
protected array $rules = [
protected $rules = [
'update' => [
'owner_id' => ['int'],

View File

@@ -29,7 +29,7 @@ class JointPermissionBuilder
/**
* Re-generate all entity permission from scratch.
*/
public function rebuildForAll(): void
public function rebuildForAll()
{
JointPermission::query()->truncate();
@@ -51,7 +51,7 @@ class JointPermissionBuilder
/**
* Rebuild the entity jointPermissions for a particular entity.
*/
public function rebuildForEntity(Entity $entity): void
public function rebuildForEntity(Entity $entity)
{
$entities = [$entity];
if ($entity instanceof Book) {
@@ -119,7 +119,7 @@ class JointPermissionBuilder
/**
* Build joint permissions for the given book and role combinations.
*/
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false): void
protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false)
{
$entities = clone $books;
@@ -143,7 +143,7 @@ class JointPermissionBuilder
/**
* Rebuild the entity jointPermissions for a collection of entities.
*/
protected function buildJointPermissionsForEntities(array $entities): void
protected function buildJointPermissionsForEntities(array $entities)
{
$roles = Role::query()->get()->values()->all();
$this->deleteManyJointPermissionsForEntities($entities);
@@ -155,19 +155,21 @@ class JointPermissionBuilder
*
* @param Entity[] $entities
*/
protected function deleteManyJointPermissionsForEntities(array $entities): void
protected function deleteManyJointPermissionsForEntities(array $entities)
{
$simpleEntities = $this->entitiesToSimpleEntities($entities);
$idsByType = $this->entitiesToTypeIdMap($simpleEntities);
foreach ($idsByType as $type => $ids) {
foreach (array_chunk($ids, 1000) as $idChunk) {
DB::table('joint_permissions')
->where('entity_type', '=', $type)
->whereIn('entity_id', $idChunk)
->delete();
DB::transaction(function () use ($idsByType) {
foreach ($idsByType as $type => $ids) {
foreach (array_chunk($ids, 1000) as $idChunk) {
DB::table('joint_permissions')
->where('entity_type', '=', $type)
->whereIn('entity_id', $idChunk)
->delete();
}
}
}
});
}
/**
@@ -193,7 +195,7 @@ class JointPermissionBuilder
* @param Entity[] $originalEntities
* @param Role[] $roles
*/
protected function createManyJointPermissions(array $originalEntities, array $roles): void
protected function createManyJointPermissions(array $originalEntities, array $roles)
{
$entities = $this->entitiesToSimpleEntities($originalEntities);
$jointPermissions = [];
@@ -223,9 +225,11 @@ class JointPermissionBuilder
}
}
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
DB::table('joint_permissions')->insert($jointPermissionChunk);
}
DB::transaction(function () use ($jointPermissions) {
foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
DB::table('joint_permissions')->insert($jointPermissionChunk);
}
});
}
/**

View File

@@ -7,7 +7,6 @@ use BookStack\Entities\Tools\PermissionsUpdater;
use BookStack\Http\Controller;
use BookStack\Permissions\Models\EntityPermission;
use BookStack\Users\Models\Role;
use BookStack\Util\DatabaseTransaction;
use Illuminate\Http\Request;
class PermissionsController extends Controller
@@ -41,9 +40,7 @@ class PermissionsController extends Controller
$page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$this->checkOwnablePermission('restrictions-manage', $page);
(new DatabaseTransaction(function () use ($page, $request) {
$this->permissionsUpdater->updateFromPermissionsForm($page, $request);
}))->run();
$this->permissionsUpdater->updateFromPermissionsForm($page, $request);
$this->showSuccessNotification(trans('entities.pages_permissions_success'));
@@ -73,9 +70,7 @@ class PermissionsController extends Controller
$chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
$this->checkOwnablePermission('restrictions-manage', $chapter);
(new DatabaseTransaction(function () use ($chapter, $request) {
$this->permissionsUpdater->updateFromPermissionsForm($chapter, $request);
}))->run();
$this->permissionsUpdater->updateFromPermissionsForm($chapter, $request);
$this->showSuccessNotification(trans('entities.chapters_permissions_success'));
@@ -105,9 +100,7 @@ class PermissionsController extends Controller
$book = $this->queries->books->findVisibleBySlugOrFail($slug);
$this->checkOwnablePermission('restrictions-manage', $book);
(new DatabaseTransaction(function () use ($book, $request) {
$this->permissionsUpdater->updateFromPermissionsForm($book, $request);
}))->run();
$this->permissionsUpdater->updateFromPermissionsForm($book, $request);
$this->showSuccessNotification(trans('entities.books_permissions_updated'));
@@ -137,9 +130,7 @@ class PermissionsController extends Controller
$shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug);
$this->checkOwnablePermission('restrictions-manage', $shelf);
(new DatabaseTransaction(function () use ($shelf, $request) {
$this->permissionsUpdater->updateFromPermissionsForm($shelf, $request);
}))->run();
$this->permissionsUpdater->updateFromPermissionsForm($shelf, $request);
$this->showSuccessNotification(trans('entities.shelves_permissions_updated'));
@@ -154,10 +145,7 @@ class PermissionsController extends Controller
$shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug);
$this->checkOwnablePermission('restrictions-manage', $shelf);
$updateCount = (new DatabaseTransaction(function () use ($shelf) {
return $this->permissionsUpdater->updateBookPermissionsFromShelf($shelf);
}))->run();
$updateCount = $this->permissionsUpdater->updateBookPermissionsFromShelf($shelf);
$this->showSuccessNotification(trans('entities.shelves_copy_permission_success', ['count' => $updateCount]));
return redirect($shelf->getUrl());

View File

@@ -7,7 +7,6 @@ use BookStack\Exceptions\PermissionsException;
use BookStack\Facades\Activity;
use BookStack\Permissions\Models\RolePermission;
use BookStack\Users\Models\Role;
use BookStack\Util\DatabaseTransaction;
use Exception;
use Illuminate\Database\Eloquent\Collection;
@@ -49,42 +48,38 @@ class PermissionsRepo
*/
public function saveNewRole(array $roleData): Role
{
return (new DatabaseTransaction(function () use ($roleData) {
$role = new Role($roleData);
$role->mfa_enforced = boolval($roleData['mfa_enforced'] ?? false);
$role->save();
$role = new Role($roleData);
$role->mfa_enforced = boolval($roleData['mfa_enforced'] ?? false);
$role->save();
$permissions = $roleData['permissions'] ?? [];
$this->assignRolePermissions($role, $permissions);
$this->permissionBuilder->rebuildForRole($role);
$permissions = $roleData['permissions'] ?? [];
$this->assignRolePermissions($role, $permissions);
$this->permissionBuilder->rebuildForRole($role);
Activity::add(ActivityType::ROLE_CREATE, $role);
Activity::add(ActivityType::ROLE_CREATE, $role);
return $role;
}))->run();
return $role;
}
/**
* Updates an existing role.
* Ensures the Admin system role always has core permissions.
* Ensures Admin system role always have core permissions.
*/
public function updateRole($roleId, array $roleData): Role
{
$role = $this->getRoleById($roleId);
return (new DatabaseTransaction(function () use ($role, $roleData) {
if (isset($roleData['permissions'])) {
$this->assignRolePermissions($role, $roleData['permissions']);
}
if (isset($roleData['permissions'])) {
$this->assignRolePermissions($role, $roleData['permissions']);
}
$role->fill($roleData);
$role->save();
$this->permissionBuilder->rebuildForRole($role);
$role->fill($roleData);
$role->save();
$this->permissionBuilder->rebuildForRole($role);
Activity::add(ActivityType::ROLE_UPDATE, $role);
Activity::add(ActivityType::ROLE_UPDATE, $role);
return $role;
}))->run();
return $role;
}
/**
@@ -119,7 +114,7 @@ class PermissionsRepo
/**
* Delete a role from the system.
* Check it's not an admin role or set as default before deleting.
* If a migration Role ID is specified, the users assigned to the current role
* If a migration Role ID is specified the users assign to the current role
* will be added to the role of the specified id.
*
* @throws PermissionsException
@@ -136,19 +131,17 @@ class PermissionsRepo
throw new PermissionsException(trans('errors.role_registration_default_cannot_delete'));
}
(new DatabaseTransaction(function () use ($migrateRoleId, $role) {
if ($migrateRoleId !== 0) {
$newRole = Role::query()->find($migrateRoleId);
if ($newRole) {
$users = $role->users()->pluck('id')->toArray();
$newRole->users()->sync($users);
}
if ($migrateRoleId !== 0) {
$newRole = Role::query()->find($migrateRoleId);
if ($newRole) {
$users = $role->users()->pluck('id')->toArray();
$newRole->users()->sync($users);
}
}
$role->entityPermissions()->delete();
$role->jointPermissions()->delete();
Activity::add(ActivityType::ROLE_DELETE, $role);
$role->delete();
}))->run();
$role->entityPermissions()->delete();
$role->jointPermissions()->delete();
Activity::add(ActivityType::ROLE_DELETE, $role);
$role->delete();
}
}

View File

@@ -9,7 +9,7 @@ use Illuminate\Http\Request;
class SearchApiController extends ApiController
{
protected array $rules = [
protected $rules = [
'all' => [
'query' => ['required'],
'page' => ['integer', 'min:1'],

View File

@@ -7,7 +7,6 @@ use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Tools\BookContents;
use BookStack\Facades\Activity;
use BookStack\Http\Controller;
use BookStack\Util\DatabaseTransaction;
use Illuminate\Http\Request;
class BookSortController extends Controller
@@ -56,18 +55,16 @@ class BookSortController extends Controller
// Sort via map
if ($request->filled('sort-tree')) {
(new DatabaseTransaction(function () use ($book, $request, $sorter, &$loggedActivityForBook) {
$sortMap = BookSortMap::fromJson($request->get('sort-tree'));
$booksInvolved = $sorter->sortUsingMap($sortMap);
$sortMap = BookSortMap::fromJson($request->get('sort-tree'));
$booksInvolved = $sorter->sortUsingMap($sortMap);
// Add activity for involved books.
foreach ($booksInvolved as $bookInvolved) {
Activity::add(ActivityType::BOOK_SORT, $bookInvolved);
if ($bookInvolved->id === $book->id) {
$loggedActivityForBook = true;
}
// Rebuild permissions and add activity for involved books.
foreach ($booksInvolved as $bookInvolved) {
Activity::add(ActivityType::BOOK_SORT, $bookInvolved);
if ($bookInvolved->id === $book->id) {
$loggedActivityForBook = true;
}
}))->run();
}
}
if ($request->filled('auto-sort')) {

View File

@@ -2,6 +2,7 @@
namespace BookStack\Sorting;
use BookStack\App\Model;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Chapter;

View File

@@ -50,7 +50,6 @@ class LocaleManager
'ku' => 'ku_TR',
'lt' => 'lt_LT',
'lv' => 'lv_LV',
'ne' => 'ne_NP',
'nb' => 'nb_NO',
'nl' => 'nl_NL',
'nn' => 'nn_NO',

View File

@@ -0,0 +1,122 @@
<?php
namespace BookStack\Uploads;
use BookStack\Exceptions\DrawioPngReaderException;
/**
* Reads the PNG file format: https://www.w3.org/TR/2003/REC-PNG-20031110/
* So that it can extract embedded drawing data for alternative use.
*/
class DrawioPngReader
{
/**
* @param resource $fileStream
*/
public function __construct(
protected $fileStream
) {
}
/**
* @throws DrawioPngReaderException
*/
public function extractDrawing(): string
{
$signature = fread($this->fileStream, 8);
$pngSignature = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
if ($signature !== $pngSignature) {
throw new DrawioPngReaderException('File does not appear to be a valid PNG file');
}
$offset = 8;
$searching = true;
while ($searching) {
fseek($this->fileStream, $offset);
$lengthBytes = $this->readData(4);
$chunkTypeBytes = $this->readData(4);
$length = unpack('Nvalue', $lengthBytes)['value'];
if ($chunkTypeBytes === 'tEXt') {
fseek($this->fileStream, $offset + 8);
$data = $this->readData($length);
$crc = $this->readData(4);
$drawingData = $this->readTextForDrawing($data);
if ($drawingData !== null) {
$crcResult = $this->calculateCrc($chunkTypeBytes . $data);
if ($crc !== $crcResult) {
throw new DrawioPngReaderException('Drawing data withing PNG file appears to be corrupted');
}
return $drawingData;
}
} else if ($chunkTypeBytes === 'IEND') {
$searching = false;
}
$offset += 12 + $length; // 12 = length + type + crc bytes
}
throw new DrawioPngReaderException('Unable to find drawing data within PNG file');
}
protected function readTextForDrawing(string $data): ?string
{
// Check the keyword is mxfile to ensure we're getting the right data
if (!str_starts_with($data, "mxfile\u{0}")) {
return null;
}
// Extract & cleanup the drawing text
$drawingText = substr($data, 7);
return urldecode($drawingText);
}
protected function readData(int $length): string
{
$bytes = fread($this->fileStream, $length);
if ($bytes === false || strlen($bytes) < $length) {
throw new DrawioPngReaderException('Unable to find drawing data within PNG file');
}
return $bytes;
}
protected function getCrcTable(): array
{
$table = [];
for ($n = 0; $n < 256; $n++) {
$c = $n;
for ($k = 0; $k < 8; $k++) {
if ($c & 1) {
$c = 0xedb88320 ^ ($c >> 1);
} else {
$c = $c >> 1;
}
}
$table[$n] = $c;
}
return $table;
}
/**
* Calculate a CRC for the given bytes following:
* https://www.w3.org/TR/2003/REC-PNG-20031110/#D-CRCAppendix
*/
protected function calculateCrc(string $bytes): string
{
$table = $this->getCrcTable();
$length = strlen($bytes);
$c = 0xffffffff;
for ($n = 0; $n < $length; $n++) {
$tableIndex = ($c ^ ord($bytes[$n])) & 0xff;
$c = $table[$tableIndex] ^ ($c >> 8);
}
return pack('N', $c ^ 0xffffffff);
}
}

View File

@@ -16,7 +16,7 @@ class RoleApiController extends ApiController
'display_name', 'description', 'mfa_enforced', 'external_auth_id', 'created_at', 'updated_at',
];
protected array $rules = [
protected $rules = [
'create' => [
'display_name' => ['required', 'string', 'min:3', 'max:180'],
'description' => ['string', 'max:180'],

View File

@@ -7,8 +7,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $created_by
* @property int $updated_by
* @property ?User $createdBy
* @property ?User $updatedBy
*/
trait HasCreatorAndUpdater
{

View File

@@ -1,42 +0,0 @@
<?php
namespace BookStack\Util;
use Closure;
use Illuminate\Support\Facades\DB;
use Throwable;
/**
* Run the given code within a database transactions.
* Wraps Laravel's own transaction method, but sets a specific runtime isolation method.
* This sets a session level since this won't cause issues if already within a transaction,
* and this should apply to the next transactions anyway.
*
* "READ COMMITTED" ensures that changes from other transactions can be read within
* a transaction, even if started afterward (and for example, it was blocked by the initial
* transaction). This is quite important for things like permission generation, where we would
* want to consider the changes made by other committed transactions by the time we come to
* regenerate permission access.
*
* @throws Throwable
* @template TReturn of mixed
*/
class DatabaseTransaction
{
/**
* @param (Closure(static): TReturn) $callback
*/
public function __construct(
protected Closure $callback
) {
}
/**
* @return TReturn
*/
public function run(): mixed
{
DB::statement('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED');
return DB::transaction($this->callback);
}
}

View File

@@ -4,6 +4,7 @@ namespace BookStack\Util;
use DOMAttr;
use DOMElement;
use DOMNamedNodeMap;
use DOMNode;
/**
@@ -24,7 +25,6 @@ class HtmlDescriptionFilter
'ul' => [],
'li' => [],
'strong' => [],
'span' => [],
'em' => [],
'br' => [],
];
@@ -59,6 +59,7 @@ class HtmlDescriptionFilter
return;
}
/** @var DOMNamedNodeMap $attrs */
$attrs = $element->attributes;
for ($i = $attrs->length - 1; $i >= 0; $i--) {
/** @var DOMAttr $attr */
@@ -69,8 +70,7 @@ class HtmlDescriptionFilter
}
}
$childNodes = [...$element->childNodes];
foreach ($childNodes as $child) {
foreach ($element->childNodes as $child) {
if ($child instanceof DOMElement) {
static::filterElement($child);
}

View File

@@ -38,7 +38,8 @@
"socialiteproviders/microsoft-azure": "^5.1",
"socialiteproviders/okta": "^4.2",
"socialiteproviders/twitch": "^5.3",
"ssddanbrown/htmldiff": "^2.0.0"
"ssddanbrown/htmldiff": "^1.0.2",
"ssddanbrown/symfony-mailer": "7.2.x-dev"
},
"require-dev": {
"fakerphp/faker": "^1.21",

935
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@
namespace Database\Factories\Entities\Models;
use BookStack\Entities\Tools\PageEditorType;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
@@ -30,7 +29,6 @@ class PageFactory extends Factory
'html' => $html,
'text' => strip_tags($html),
'revision_count' => 1,
'editor' => 'wysiwyg',
];
}
}

View File

@@ -24,7 +24,6 @@ class ImportFactory extends Factory
'path' => 'uploads/files/imports/' . Str::random(10) . '.zip',
'name' => $this->faker->words(3, true),
'type' => 'book',
'size' => rand(1, 1001),
'metadata' => '{"name": "My book"}',
'created_at' => User::factory(),
];

View File

@@ -1,4 +0,0 @@
{
"parent_type": "book",
"parent_id": 28
}

View File

@@ -1,10 +0,0 @@
{
"type": "chapter",
"name": "Pension Providers",
"created_by": 1,
"size": 2757,
"path": "uploads\/files\/imports\/ghnxmS3u9QxLWu82.zip",
"updated_at": "2025-07-18T14:50:27.000000Z",
"created_at": "2025-07-18T14:50:27.000000Z",
"id": 31
}

View File

@@ -1,23 +0,0 @@
{
"data": [
{
"id": 25,
"name": "IT Department",
"size": 618462,
"type": "book",
"created_by": 1,
"created_at": "2024-12-20T18:40:38.000000Z",
"updated_at": "2024-12-20T18:40:38.000000Z"
},
{
"id": 27,
"name": "Clients",
"size": 15364,
"type": "chapter",
"created_by": 1,
"created_at": "2025-03-20T12:41:44.000000Z",
"updated_at": "2025-03-20T12:41:44.000000Z"
}
],
"total": 2
}

View File

@@ -1,51 +0,0 @@
{
"id": 25,
"name": "IT Department",
"path": "uploads\/files\/imports\/7YOpZ6sGIEbYdRFL.zip",
"size": 618462,
"type": "book",
"created_by": 1,
"created_at": "2024-12-20T18:40:38.000000Z",
"updated_at": "2024-12-20T18:40:38.000000Z",
"details": {
"id": 4,
"name": "IT Department",
"chapters": [
{
"id": 3,
"name": "Server Systems",
"priority": 1,
"pages": [
{
"id": 22,
"name": "prod-aws-stonehawk",
"priority": 0,
"attachments": [],
"images": [],
"tags": []
}
],
"tags": []
}
],
"pages": [
{
"id": 23,
"name": "Member Onboarding Guide",
"priority": 0,
"attachments": [],
"images": [],
"tags": []
},
{
"id": 25,
"name": "IT Holiday Party Event",
"priority": 2,
"attachments": [],
"images": [],
"tags": []
}
],
"tags": []
}
}

View File

@@ -1,14 +0,0 @@
{
"id": 1067,
"book_id": 28,
"slug": "pension-providers",
"name": "Pension Providers",
"description": "Details on the various pension providers that are available",
"priority": 7,
"created_at": "2025-07-18T14:53:35.000000Z",
"updated_at": "2025-07-18T14:53:36.000000Z",
"created_by": 1,
"updated_by": 1,
"owned_by": 1,
"default_template_id": null
}

View File

@@ -13,7 +13,7 @@ const entryPoints = {
app: path.join(__dirname, '../../resources/js/app.ts'),
code: path.join(__dirname, '../../resources/js/code/index.mjs'),
'legacy-modes': path.join(__dirname, '../../resources/js/code/legacy-modes.mjs'),
markdown: path.join(__dirname, '../../resources/js/markdown/index.mts'),
markdown: path.join(__dirname, '../../resources/js/markdown/index.mjs'),
wysiwyg: path.join(__dirname, '../../resources/js/wysiwyg/index.ts'),
};

View File

@@ -1 +1 @@
fa162564940d9a81e4dd0b20ae8775d32f2ea5c615e33ebcc8adf035d958c352
22e02ee72d21ff719c1073abbec8302f8e2096ba6d072e133051064ed24b45b1

View File

@@ -345,7 +345,7 @@ Link: tj/co
codemirror
License: MIT
License File: node_modules/codemirror/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <*******@*****.***> and others
Source: https://github.com/codemirror/basic-setup.git
Link: https://github.com/codemirror/basic-setup.git
-----------
@@ -711,13 +711,13 @@ eslint-scope
License: BSD-2-Clause
License File: node_modules/eslint-scope/LICENSE
Copyright: Copyright (C) 2012-2013 Yusuke Suzuki (twitter: @Constellation) and other contributors.
Source: https://github.com/eslint/js.git
Source: eslint/js
Link: https://github.com/eslint/js/blob/main/packages/eslint-scope/README.md
-----------
eslint-visitor-keys
License: Apache-2.0
License File: node_modules/eslint-visitor-keys/LICENSE
Source: https://github.com/eslint/js.git
Source: eslint/js
Link: https://github.com/eslint/js/blob/main/packages/eslint-visitor-keys/README.md
-----------
eslint
@@ -731,7 +731,7 @@ License: BSD-2-Clause
License File: node_modules/espree/LICENSE
Copyright: Copyright (c) Open JS Foundation
All rights reserved.
Source: https://github.com/eslint/js.git
Source: eslint/js
Link: https://github.com/eslint/js/blob/main/packages/espree/README.md
-----------
esprima
@@ -1252,13 +1252,6 @@ Copyright: Copyright (c) 2019 Inspect JS
Source: git+https://github.com/inspect-js/is-map.git
Link: https://github.com/inspect-js/is-map#readme
-----------
is-negative-zero
License: MIT
License File: node_modules/is-negative-zero/LICENSE
Copyright: Copyright (c) 2014 Jordan Harband
Source: git://github.com/inspect-js/is-negative-zero.git
Link: https://github.com/inspect-js/is-negative-zero
-----------
is-number-object
License: MIT
License File: node_modules/is-number-object/LICENSE
@@ -2500,13 +2493,6 @@ Copyright: Copyright (c) 2016-2022 Isaac Z. Schlueter <*@***.**>, James Talmage
Source: tapjs/stack-utils
Link: tapjs/stack-utils
-----------
stop-iteration-iterator
License: MIT
License File: node_modules/stop-iteration-iterator/LICENSE
Copyright: Copyright (c) 2023 Jordan Harband
Source: git+https://github.com/ljharb/stop-iteration-iterator.git
Link: https://github.com/ljharb/stop-iteration-iterator#readme
-----------
string-length
License: MIT
License File: node_modules/string-length/license
@@ -3006,13 +2992,6 @@ Copyright: Copyright (c) 2014-present Sebastian McKenzie and other contributors
Source: https://github.com/babel/babel.git
Link: https://github.com/babel/babel.git
-----------
@babel/helper-globals
License: MIT
License File: node_modules/@babel/helper-globals/LICENSE
Copyright: Copyright (c) 2014-present Sebastian McKenzie and other contributors
Source: https://github.com/babel/babel.git
Link: https://github.com/babel/babel.git
-----------
@babel/helper-module-imports
License: MIT
License File: node_modules/@babel/helper-module-imports/LICENSE
@@ -3059,7 +3038,7 @@ Link: https://github.com/babel/babel.git
License: MIT
License File: node_modules/@babel/helpers/LICENSE
Copyright: Copyright (c) 2014-present Sebastian McKenzie and other contributors
Copyright (c) 2014-present, Facebook, Inc. (ONLY ./src/helpers/regenerator* files)
Copyright (c) 2014-present, Facebook, Inc. (ONLY ./src/helpers/regeneratorRuntime.js)
Source: https://github.com/babel/babel.git
Link: https://babel.dev/docs/en/next/babel-helpers
-----------
@@ -3254,7 +3233,7 @@ Link: https://github.com/codemirror/lang-javascript.git
@codemirror/lang-json
License: MIT
License File: node_modules/@codemirror/lang-json/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <*******@*****.***> and others
Source: https://github.com/codemirror/lang-json.git
Link: https://github.com/codemirror/lang-json.git
-----------
@@ -3268,7 +3247,7 @@ Link: https://github.com/codemirror/lang-markdown.git
@codemirror/lang-php
License: MIT
License File: node_modules/@codemirror/lang-php/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <*******@*****.***> and others
Source: https://github.com/codemirror/lang-php.git
Link: https://github.com/codemirror/lang-php.git
-----------
@@ -3358,7 +3337,7 @@ Link: https://github.com/eslint-community/regexpp#readme
License: Apache-2.0
License File: node_modules/@eslint/config-array/LICENSE
Source: git+https://github.com/eslint/rewrite.git
Link: https://github.com/eslint/rewrite/tree/main/packages/config-array#readme
Link: https://github.com/eslint/rewrite#readme
-----------
@eslint/config-helpers
License: Apache-2.0
@@ -3370,7 +3349,7 @@ Link: https://github.com/eslint/rewrite/tree/main/packages/config-helpers#readme
License: Apache-2.0
License File: node_modules/@eslint/core/LICENSE
Source: git+https://github.com/eslint/rewrite.git
Link: https://github.com/eslint/rewrite/tree/main/packages/core#readme
Link: https://github.com/eslint/rewrite#readme
-----------
@eslint/eslintrc
License: MIT
@@ -3394,7 +3373,7 @@ Link: https://github.com/eslint/rewrite#readme
License: Apache-2.0
License File: node_modules/@eslint/plugin-kit/LICENSE
Source: git+https://github.com/eslint/rewrite.git
Link: https://github.com/eslint/rewrite/tree/main/packages/plugin-kit#readme
Link: https://github.com/eslint/rewrite#readme
-----------
@humanfs/core
License: Apache-2.0
@@ -3535,9 +3514,9 @@ Link: https://github.com/jestjs/jest.git
@jridgewell/gen-mapping
License: MIT
License File: node_modules/@jridgewell/gen-mapping/LICENSE
Copyright: Copyright 2024 Justin Ridgewell <******@*********.****>
Source: git+https://github.com/jridgewell/sourcemaps.git
Link: https://github.com/jridgewell/sourcemaps/tree/main/packages/gen-mapping
Copyright: Copyright 2022 Justin Ridgewell <**********@******.***>
Source: https://github.com/jridgewell/gen-mapping
Link: https://github.com/jridgewell/gen-mapping
-----------
@jridgewell/resolve-uri
License: MIT
@@ -3546,19 +3525,26 @@ Copyright: Copyright 2019 Justin Ridgewell <**********@******.***>
Source: https://github.com/jridgewell/resolve-uri
Link: https://github.com/jridgewell/resolve-uri
-----------
@jridgewell/set-array
License: MIT
License File: node_modules/@jridgewell/set-array/LICENSE
Copyright: Copyright 2022 Justin Ridgewell <**********@******.***>
Source: https://github.com/jridgewell/set-array
Link: https://github.com/jridgewell/set-array
-----------
@jridgewell/sourcemap-codec
License: MIT
License File: node_modules/@jridgewell/sourcemap-codec/LICENSE
Copyright: Copyright 2024 Justin Ridgewell <******@*********.****>
Source: git+https://github.com/jridgewell/sourcemaps.git
Link: https://github.com/jridgewell/sourcemaps/tree/main/packages/sourcemap-codec
Copyright: Copyright (c) 2015 Rich Harris
Source: git+https://github.com/jridgewell/sourcemap-codec.git
Link: git+https://github.com/jridgewell/sourcemap-codec.git
-----------
@jridgewell/trace-mapping
License: MIT
License File: node_modules/@jridgewell/trace-mapping/LICENSE
Copyright: Copyright 2024 Justin Ridgewell <******@*********.****>
Source: git+https://github.com/jridgewell/sourcemaps.git
Link: https://github.com/jridgewell/sourcemaps/tree/main/packages/trace-mapping
Copyright: Copyright 2022 Justin Ridgewell <******@*********.****>
Source: git+https://github.com/jridgewell/trace-mapping.git
Link: git+https://github.com/jridgewell/trace-mapping.git
-----------
@lezer/common
License: MIT
@@ -3827,27 +3813,6 @@ License: MIT
Source: https://www.github.com/DefinitelyTyped/DefinitelyTyped.git
Link: https://www.github.com/DefinitelyTyped/DefinitelyTyped.git
-----------
@types/linkify-it
License: MIT
License File: node_modules/@types/linkify-it/LICENSE
Copyright: Copyright (c) Microsoft Corporation.
Source: https://github.com/DefinitelyTyped/DefinitelyTyped.git
Link: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/linkify-it
-----------
@types/markdown-it
License: MIT
License File: node_modules/@types/markdown-it/LICENSE
Copyright: Copyright (c) Microsoft Corporation.
Source: https://github.com/DefinitelyTyped/DefinitelyTyped.git
Link: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/markdown-it
-----------
@types/mdurl
License: MIT
License File: node_modules/@types/mdurl/LICENSE
Copyright: Copyright (c) Microsoft Corporation.
Source: https://github.com/DefinitelyTyped/DefinitelyTyped.git
Link: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mdurl
-----------
@types/node
License: MIT
License File: node_modules/@types/node/LICENSE

View File

@@ -467,7 +467,7 @@ License: MIT
License File: vendor/psy/psysh/LICENSE
Copyright: Copyright (c) 2012-2023 Justin Hileman
Source: https://github.com/bobthecow/psysh.git
Link: https://psysh.org
Link: http://psysh.org
-----------
ralouphie/getallheaders
License: MIT
@@ -543,6 +543,13 @@ Copyright: Copyright (c) 2024 Nathan Herald, Rohland de Charmoy, Dan Brown
Source: https://codeberg.org/danb/HtmlDiff
Link: https://codeberg.org/danb/HtmlDiff
-----------
ssddanbrown/symfony-mailer
License: MIT
License File: vendor/ssddanbrown/symfony-mailer/LICENSE
Copyright: Copyright (c) 2019-present Fabien Potencier
Source: https://github.com/ssddanbrown/symfony-mailer.git
Link: https://symfony.com
-----------
symfony/clock
License: MIT
License File: vendor/symfony/clock/LICENSE
@@ -613,13 +620,6 @@ Copyright: Copyright (c) 2004-present Fabien Potencier
Source: https://github.com/symfony/http-kernel.git
Link: https://symfony.com
-----------
symfony/mailer
License: MIT
License File: vendor/symfony/mailer/LICENSE
Copyright: Copyright (c) 2019-present Fabien Potencier
Source: https://github.com/symfony/mailer.git
Link: https://symfony.com
-----------
symfony/mime
License: MIT
License File: vendor/symfony/mime/LICENSE

View File

@@ -30,8 +30,8 @@ return [
'create' => 'إنشاء',
'update' => 'تحديث',
'edit' => 'تعديل',
'archive' => 'أرشف',
'unarchive' => 'إلغاء الأرشفة',
'archive' => 'Archive',
'unarchive' => 'Un-Archive',
'sort' => 'سرد',
'move' => 'نقل',
'copy' => 'نسخ',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'نص مرتفع',
'subscript' => 'نص منخفض',
'text_color' => 'لون النص',
'highlight_color' => 'لون التمييز',
'custom_color' => 'لون مخصص',
'remove_color' => 'إزالة اللون',
'background_color' => 'لون الخلفية',

View File

@@ -63,10 +63,6 @@ return [
'import_delete_desc' => 'سيؤدي هذا إلى حذف مِلَفّ الاستيراد المضغوط ZIP، ولا يمكن التراجع عنه.',
'import_errors' => 'أخطاء الاستيراد',
'import_errors_desc' => 'حدثت الأخطاء التالية خلال محاولة الاستيراد:',
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
// Permissions and restrictions
'permissions' => 'الأذونات',
@@ -252,7 +248,7 @@ return [
'pages_edit_switch_to_markdown_stable' => '(محتوى مستقر)',
'pages_edit_switch_to_wysiwyg' => 'التبديل إلى محرر ما تراه هو ما تحصل عليه -WYSIWYG-',
'pages_edit_switch_to_new_wysiwyg' => 'التبديل إلى محرر ما تراه هو ما تحصل عليه الجديد -new WYSIWYG-',
'pages_edit_switch_to_new_wysiwyg_desc' => '(في الاختبار التجريبي)',
'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)',
'pages_edit_set_changelog' => 'تثبيت سجل التعديل',
'pages_edit_enter_changelog_desc' => 'ضع وصف مختصر للتعديلات التي تمت',
'pages_edit_enter_changelog' => 'أدخل سجل التعديل',
@@ -272,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'إدخال رسمة',
'pages_md_show_preview' => 'عرض المعاينة',
'pages_md_sync_scroll' => 'مزامنة معاينة التمرير',
'pages_md_plain_editor' => 'محرر النصوص العادي',
'pages_drawing_unsaved' => 'تم العثور على رسم غير محفوظ',
'pages_drawing_unsaved_confirm' => 'تم العثور على بيانات رسم غير محفوظة من محاولة حفظ رسم سابقة فاشلة. هل ترغب في استعادة هذا الرسم غير المحفوظ ومواصلة تحريره؟',
'pages_not_in_chapter' => 'صفحة ليست في فصل',

View File

@@ -348,7 +348,6 @@ return [
'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nb' => 'Norsk (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Nederlands',
'pl' => 'Polski',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Горен индекс',
'subscript' => 'Долен индекс',
'text_color' => 'Цвят на текста',
'highlight_color' => 'Highlight color',
'custom_color' => 'Цвят по избор',
'remove_color' => 'Премахване на цвят',
'background_color' => 'Фонов цвят',

View File

@@ -63,10 +63,6 @@ return [
'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.',
'import_errors' => 'Import Errors',
'import_errors_desc' => 'The follow errors occurred during the import attempt:',
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
// Permissions and restrictions
'permissions' => 'Права',
@@ -272,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Вмъкни рисунка',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_plain_editor' => 'Plaintext editor',
'pages_drawing_unsaved' => 'Unsaved Drawing Found',
'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?',
'pages_not_in_chapter' => 'Страницата не принадлежи в никоя глава',

View File

@@ -348,7 +348,6 @@ return [
'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nb' => 'Norsk (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Nederlands',
'pl' => 'Polski',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Superscript',
'subscript' => 'Subscript',
'text_color' => 'Text color',
'highlight_color' => 'Highlight color',
'custom_color' => 'Custom color',
'remove_color' => 'Remove color',
'background_color' => 'Background color',

View File

@@ -63,10 +63,6 @@ return [
'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.',
'import_errors' => 'Import Errors',
'import_errors_desc' => 'The follow errors occurred during the import attempt:',
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
// Permissions and restrictions
'permissions' => 'Permissions',
@@ -272,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Insert Drawing',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_plain_editor' => 'Plaintext editor',
'pages_drawing_unsaved' => 'Unsaved Drawing Found',
'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?',
'pages_not_in_chapter' => 'Page is not in a chapter',

View File

@@ -348,7 +348,6 @@ return [
'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nb' => 'Norsk (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Nederlands',
'pl' => 'Polski',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Superscript',
'subscript' => 'Subscript',
'text_color' => 'Text color',
'highlight_color' => 'Highlight color',
'custom_color' => 'Custom color',
'remove_color' => 'Remove color',
'background_color' => 'Background color',

View File

@@ -63,10 +63,6 @@ return [
'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.',
'import_errors' => 'Import Errors',
'import_errors_desc' => 'The follow errors occurred during the import attempt:',
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
// Permissions and restrictions
'permissions' => 'Dozvole',
@@ -272,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Insert Drawing',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
'pages_md_plain_editor' => 'Plaintext editor',
'pages_drawing_unsaved' => 'Unsaved Drawing Found',
'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?',
'pages_not_in_chapter' => 'Page is not in a chapter',

View File

@@ -348,7 +348,6 @@ return [
'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nb' => 'Norsk (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Nederlands',
'pl' => 'Polski',

View File

@@ -18,35 +18,35 @@ return [
'page_move_notification' => 'Sha mogut la pàgina',
// Chapters
'chapter_create' => 'S\'ha creat el capítol',
'chapter_create' => 'ha creat el capítol',
'chapter_create_notification' => 'Sha creat el capítol',
'chapter_update' => 'ha actualitzat el capítol',
'chapter_update_notification' => 'Sha actualitzat el capítol',
'chapter_delete' => 'ha suprimit el capítol',
'chapter_delete_notification' => 'Sha suprimit el capítol',
'chapter_move' => 's\'ha mogut el capítol',
'chapter_move' => 'ha mogut el capítol',
'chapter_move_notification' => 'Sha mogut el capítol',
// Books
'book_create' => 'llibre creat',
'book_create' => 'ha creat el llibre',
'book_create_notification' => 'Sha creat el llibre',
'book_create_from_chapter' => 'ha convertit el capítol a llibre',
'book_create_from_chapter_notification' => 'Sha convertit el capítol a llibre',
'book_update' => 'llibre actualitzat',
'book_update' => 'ha actualitzat el llibre',
'book_update_notification' => 'Sha actualitzat el llibre',
'book_delete' => 'llibre suprimit',
'book_delete' => 'ha suprimit el llibre',
'book_delete_notification' => 'Sha suprimit el llibre',
'book_sort' => 'llibre ordenat',
'book_sort' => 'ha ordenat el llibre',
'book_sort_notification' => 'Sha tornat a ordenar el llibre',
// Bookshelves
'bookshelf_create' => 'ha creat el prestatge',
'bookshelf_create_notification' => 'Sha creat el prestatge',
'bookshelf_create_from_book' => 'llibre convertit a prestatge',
'bookshelf_create_from_book' => 'ha convertit el llibre a prestatge',
'bookshelf_create_from_book_notification' => 'Sha convertit el llibre a prestatge',
'bookshelf_update' => 'prestatge actualitzat',
'bookshelf_update' => 'ha actualitzat el prestatge',
'bookshelf_update_notification' => 'Sha actualitzat el prestatge',
'bookshelf_delete' => 'prestatge suprimit',
'bookshelf_delete' => 'ha suprimit el prestatge',
'bookshelf_delete_notification' => 'Sha suprimit el prestatge',
// Revisions
@@ -85,12 +85,12 @@ return [
'webhook_delete_notification' => 'Sha suprimit el webhook',
// Imports
'import_create' => 'importació creada',
'import_create_notification' => 'L\'importació s\'ha carregat correctament',
'import_run' => 'importació actualitzada',
'import_run_notification' => 'Contingut importat correctament',
'import_delete' => 'importació eliminada',
'import_delete_notification' => 'Importació eliminada correctament',
'import_create' => 'created import',
'import_create_notification' => 'Import successfully uploaded',
'import_run' => 'updated import',
'import_run_notification' => 'Content successfully imported',
'import_delete' => 'deleted import',
'import_delete_notification' => 'Import successfully deleted',
// Users
'user_create' => 'ha creat lusuari',
@@ -128,12 +128,12 @@ return [
'comment_delete' => 'ha suprimit un comentari',
// Sort Rules
'sort_rule_create' => 'crear regla d\'ordenació',
'sort_rule_create_notification' => 'Regla d\'ordenació creada correctament',
'sort_rule_update' => 'regla d\'ordenació actualitzada',
'sort_rule_update_notification' => 'Regla d\'ordenació actualitzada correctament',
'sort_rule_delete' => 'regla d\'ordenació eliminada',
'sort_rule_delete_notification' => 'Regla d\'ordenació eliminada correctament',
'sort_rule_create' => 'created sort rule',
'sort_rule_create_notification' => 'Sort rule successfully created',
'sort_rule_update' => 'updated sort rule',
'sort_rule_update_notification' => 'Sort rule successfully updated',
'sort_rule_delete' => 'deleted sort rule',
'sort_rule_delete_notification' => 'Sort rule successfully deleted',
// Other
'permissions_update' => 'ha actualitzat els permisos',

View File

@@ -30,8 +30,8 @@ return [
'create' => 'Crea',
'update' => 'Actualitza',
'edit' => 'Edita',
'archive' => 'Arxivar',
'unarchive' => 'Desarxivar',
'archive' => 'Archive',
'unarchive' => 'Un-Archive',
'sort' => 'Ordena',
'move' => 'Mou',
'copy' => 'Copia',
@@ -111,5 +111,5 @@ return [
'terms_of_service' => 'Condicions del servei',
// OpenSearch
'opensearch_description' => 'Buscar :appName',
'opensearch_description' => 'Search :appName',
];

View File

@@ -13,7 +13,7 @@ return [
'cancel' => 'Cancel·la',
'save' => 'Desa',
'close' => 'Tanca',
'apply' => 'Aplicar',
'apply' => 'Apply',
'undo' => 'Desfés',
'redo' => 'Refés',
'left' => 'Esquerra',
@@ -25,7 +25,7 @@ return [
'width' => 'Amplada',
'height' => 'Altura',
'More' => 'Més',
'select' => 'Selecciona …',
'select' => 'Selecciona…',
// Toolbar
'formats' => 'Formats',
@@ -48,7 +48,6 @@ return [
'superscript' => 'Superíndex',
'subscript' => 'Subíndex',
'text_color' => 'Color del text',
'highlight_color' => 'Color ressaltat',
'custom_color' => 'Color personalitzat',
'remove_color' => 'Elimina el color',
'background_color' => 'Color de fons',
@@ -149,7 +148,7 @@ return [
'url' => 'URL',
'text_to_display' => 'Text per a mostrar',
'title' => 'Títol',
'browse_links' => 'Explorar enllaços',
'browse_links' => 'Browse links',
'open_link' => 'Obre lenllaç',
'open_link_in' => 'Obre lenllaç…',
'open_link_current' => 'A la finestra actual',
@@ -166,8 +165,8 @@ return [
'about' => 'Quant a lEditor',
'about_title' => 'Quant a lEditor WYSIWYG',
'editor_license' => 'Llicència i copyright de lEditor',
'editor_lexical_license' => 'Aquest editor està construït com una bifurcació de :lexicalLink i es distribueix sota la llicència MIT.',
'editor_lexical_license_link' => 'Tots els detalls complets de la llicència es poden trobar aquí.',
'editor_lexical_license' => 'This editor is built as a fork of :lexicalLink which is distributed under the MIT license.',
'editor_lexical_license_link' => 'Full license details can be found here.',
'editor_tiny_license' => 'Aquest editor sha creat amb :tinyLink que es proporciona amb la llicència MIT.',
'editor_tiny_license_link' => 'Detalls de la llicència i el copyright de TinyMCE.',
'save_continue' => 'Desa la pàgina i continua',

View File

@@ -39,34 +39,30 @@ return [
'export_pdf' => 'Fitxer PDF',
'export_text' => 'Fitxer de text sense format',
'export_md' => 'Fitxer Markdown',
'export_zip' => 'ZIP portable',
'default_template' => 'Plantilla de pàgina per defecte',
'default_template_explain' => 'Assigna una plantilla de pàgina que s\'utilitzarà com a contingut predeterminat per a totes les pàgines creades en aquest element. Tingues en compte que això només s\'utilitzarà si el creador de pàgines té accés a la plantilla de pàgina triada.',
'default_template_select' => 'Seleccioneu una plantilla de pàgina',
'import' => 'Importar',
'import_validate' => 'Validar importació',
'import_desc' => 'Importar llibres, capítols i pàgines utilitzant una exportació ZIP portable de la mateixa o una altra instància. Selecciona un arxiu ZIP per continuar. Després que l\'arxiu s\'hagi penjat i validat, podrà configurar i confirmar l\'importació en la següent vista.',
'import_zip_select' => 'Seleccioneu un fitxer ZIP per pujar',
'import_zip_validation_errors' => 'S\'han detectat errors al validar l\'arxiu ZIP proporcionat:',
'import_pending' => 'Importació pendent',
'import_pending_none' => 'No s\'han iniciat les importacions.',
'import_continue' => 'Continuar importació',
'import_continue_desc' => 'Revisa el contingut que s\'ha d\'importar de l\'arxiu ZIP penjat. Quan estigui llest, executa l\'importació per afegir el seu contingut al sistema. L\'arxiu d\'importació ZIP penjat s\'eliminarà automàticament al finalitzar l\'importació correctament.',
'import_details' => 'Detalls d\'importació',
'import_run' => 'Executar importació',
'import_size' => ':size Mida de l\'arxiu ZIP',
'import_uploaded_at' => 'Penjat :relativeTime',
'import_uploaded_by' => 'Actualitzat per',
'import_location' => 'Importar ubicació',
'import_location_desc' => 'Selecciona una ubicació de destí pel contingut importat. Necessitarà els permisos pertinents per crear-lo dins de la ubicació triada.',
'import_delete_confirm' => 'Esteu segur que voleu suplir aquesta importació?',
'import_delete_desc' => 'Això eliminarà l\'arxiu ZIP d\'importació penjat i no es pot desfer.',
'import_errors' => 'Importar errors',
'import_errors_desc' => 'S\'han produït els següents errors durant l\'intent d\'importació:',
'breadcrumb_siblings_for_page' => 'Navegar entre pàgines del mateix nivell',
'breadcrumb_siblings_for_chapter' => 'Navegar entre capítols del mateix nivell',
'breadcrumb_siblings_for_book' => 'Navegar entre llibres del mateix nivell',
'breadcrumb_siblings_for_bookshelf' => 'Navegar entre llibreries del mateix nivell',
'export_zip' => 'Portable ZIP',
'default_template' => 'Default Page Template',
'default_template_explain' => 'Assign a page template that will be used as the default content for all pages created within this item. Keep in mind this will only be used if the page creator has view access to the chosen template page.',
'default_template_select' => 'Select a template page',
'import' => 'Import',
'import_validate' => 'Validate Import',
'import_desc' => 'Import books, chapters & pages using a portable zip export from the same, or a different, instance. Select a ZIP file to proceed. After the file has been uploaded and validated you\'ll be able to configure & confirm the import in the next view.',
'import_zip_select' => 'Select ZIP file to upload',
'import_zip_validation_errors' => 'Errors were detected while validating the provided ZIP file:',
'import_pending' => 'Pending Imports',
'import_pending_none' => 'No imports have been started.',
'import_continue' => 'Continue Import',
'import_continue_desc' => 'Review the content due to be imported from the uploaded ZIP file. When ready, run the import to add its contents to this system. The uploaded ZIP import file will be automatically removed on successful import.',
'import_details' => 'Import Details',
'import_run' => 'Run Import',
'import_size' => ':size Import ZIP Size',
'import_uploaded_at' => 'Uploaded :relativeTime',
'import_uploaded_by' => 'Uploaded by',
'import_location' => 'Import Location',
'import_location_desc' => 'Select a target location for your imported content. You\'ll need the relevant permissions to create within the location you choose.',
'import_delete_confirm' => 'Are you sure you want to delete this import?',
'import_delete_desc' => 'This will delete the uploaded import ZIP file, and cannot be undone.',
'import_errors' => 'Import Errors',
'import_errors_desc' => 'The follow errors occurred during the import attempt:',
// Permissions and restrictions
'permissions' => 'Permisos',
@@ -170,9 +166,9 @@ return [
'books_search_this' => 'Cerca en aquest llibre',
'books_navigation' => 'Navegació del llibre',
'books_sort' => 'Ordena el contingut dun llibre',
'books_sort_desc' => 'Mou capítols i pàgines dins d\'un llibre per reorganitzar el seu contingut. Es poden afegir altres llibres que permetin moure fàcilment capítols i pàgines entre llibres. De manera opcional, es poden establir regles d\'ordenació automàtica per ordenar automàticament el contingut d\'aquest llibre quan hi hagi canvis.',
'books_sort_auto_sort' => 'Opció d\'ordenació automàtica',
'books_sort_auto_sort_active' => 'Opció d\'ordenació activa :sortName',
'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books. Optionally an auto sort rule can be set to automatically sort this book\'s contents upon changes.',
'books_sort_auto_sort' => 'Auto Sort Option',
'books_sort_auto_sort_active' => 'Auto Sort Active: :sortName',
'books_sort_named' => 'Ordena el llibre &laquo;:bookName&raquo;',
'books_sort_name' => 'Ordena pel nom',
'books_sort_created' => 'Ordena per la data de creació',
@@ -234,7 +230,7 @@ return [
'pages_delete_draft' => 'Suprimeix lesborrany de pàgina',
'pages_delete_success' => 'Sha suprimit la pàgina',
'pages_delete_draft_success' => 'Sha suprimit lesborrany de pàgina',
'pages_delete_warning_template' => 'Aquesta pàgina està en ús com a plantilla de pàgina predeterminada de llibre o capítol. Aquests llibres o capítols ja no tindran una plantilla de pàgina predeterminada assignada després d\'eliminar aquesta pàgina.',
'pages_delete_warning_template' => 'This page is in active use as a book or chapter default page template. These books or chapters will no longer have a default page template assigned after this page is deleted.',
'pages_delete_confirm' => 'Esteu segur que voleu suprimir aquesta pàgina?',
'pages_delete_draft_confirm' => 'Esteu segur que voleu suprimir aquest esborrany de pàgina?',
'pages_editing_named' => 'Edició de la pàgina &laquo;:pageName&raquo;',
@@ -251,8 +247,8 @@ return [
'pages_edit_switch_to_markdown_clean' => '(Contingut net)',
'pages_edit_switch_to_markdown_stable' => '(Contingut estable)',
'pages_edit_switch_to_wysiwyg' => 'Canvia a leditor WYSIWYG',
'pages_edit_switch_to_new_wysiwyg' => 'Canviar al nou editor WYSIWYG',
'pages_edit_switch_to_new_wysiwyg_desc' => '(En Beta Test)',
'pages_edit_switch_to_new_wysiwyg' => 'Switch to new WYSIWYG',
'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)',
'pages_edit_set_changelog' => 'Registre de canvis',
'pages_edit_enter_changelog_desc' => 'Introduïu una descripció breu dels canvis que heu fet',
'pages_edit_enter_changelog' => 'Registra un canvi',
@@ -272,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Insereix un dibuix',
'pages_md_show_preview' => 'Mostra la visualització prèvia',
'pages_md_sync_scroll' => 'Sincronitza el desplaçament de la visualització prèvia',
'pages_md_plain_editor' => 'Editor de text pla',
'pages_drawing_unsaved' => 'Sha trobat un dibuix sense desar',
'pages_drawing_unsaved_confirm' => 'Shan trobat dades dun dibuix dun intent anterior de desar un dibuix. Voleu restaurar aquest dibuix no desat per a reprendren ledició?',
'pages_not_in_chapter' => 'La pàgina no és un capítol',
@@ -397,11 +392,11 @@ return [
'comment' => 'Comentari',
'comments' => 'Comentaris',
'comment_add' => 'Afegeix un comentari',
'comment_none' => 'No hi ha comentaris per mostrar',
'comment_none' => 'No comments to display',
'comment_placeholder' => 'Deixa un comentari aquí',
'comment_thread_count' => ':count fil de comentaris|:count fils de comentaris',
'comment_archived_count' => ':count Arxivats',
'comment_archived_threads' => 'Fils arxivats',
'comment_thread_count' => ':count Comment Thread|:count Comment Threads',
'comment_archived_count' => ':count Archived',
'comment_archived_threads' => 'Archived Threads',
'comment_save' => 'Desa el comentari',
'comment_new' => 'Crea un comentari',
'comment_created' => 'ha comentat :createDiff',
@@ -410,14 +405,14 @@ return [
'comment_deleted_success' => 'Sha suprimit el comentari',
'comment_created_success' => 'Sha afegit un comentari',
'comment_updated_success' => 'Sha actualitzat un comentari',
'comment_archive_success' => 'Comentari arxivat',
'comment_unarchive_success' => 'Comentari desarxivat',
'comment_view' => 'Mostra el comentari',
'comment_jump_to_thread' => 'Anar al fil',
'comment_archive_success' => 'Comment archived',
'comment_unarchive_success' => 'Comment un-archived',
'comment_view' => 'View comment',
'comment_jump_to_thread' => 'Jump to thread',
'comment_delete_confirm' => 'Esteu segur que voleu suprimir aquest comentari?',
'comment_in_reply_to' => 'En resposta a :commentId',
'comment_reference' => 'Referència',
'comment_reference_outdated' => '(Obsolet)',
'comment_reference' => 'Reference',
'comment_reference_outdated' => '(Outdated)',
'comment_editor_explain' => 'Vet aquí els comentaris que shan fet en aquesta pàgina. Els comentaris es poden fer i gestionar quan es visualitza la pàgina desada.',
// Revision

View File

@@ -6,17 +6,17 @@ return [
// Permissions
'permission' => 'No teniu permís per a accedir a la pàgina sol·licitada.',
'permissionJson' => 'No teniu permís per a executar lacció sol·licitada.',
'permissionJson' => 'No teniu permís per a fer lacció sol·licitada.',
// Auth
'error_user_exists_different_creds' => 'Ja existeix un usuari amb el correu electrònic :email però amb unes credencials diferents.',
'auth_pre_register_theme_prevention' => 'El compte d\'usuari no s\'ha pogut registrar amb els detalls proporcionats',
'email_already_confirmed' => 'Ladreça electrònica ja està confirmada. Proveu diniciar la sessió.',
'auth_pre_register_theme_prevention' => 'User account could not be registered for the provided details',
'email_already_confirmed' => 'Ja sha confirmat el correu electrònic. Proveu diniciar sessió.',
'email_confirmation_invalid' => 'Aquest testimoni de confirmació no és vàlid o ja sha utilitzat. Proveu de tornar-vos a registrar.',
'email_confirmation_expired' => 'Aquest testimoni de confirmació ha caducat. Sha enviat un altre correu electrònic de confirmació.',
'email_confirmation_awaiting' => 'Cal confirmar ladreça electrònica del compte que utilitzeu',
'email_confirmation_awaiting' => 'Cal confirmar ladreça electrònica del compte que utilitzeu.',
'ldap_fail_anonymous' => 'Laccés LDAP anònim ha fallat',
'ldap_fail_authed' => 'Laccés LDAP amb el nom distintiu i la contrasenya proporcionats ha fallat',
'ldap_fail_authed' => 'Laccés LDAP amb el nom distintiu i la contrasenya proporcionades',
'ldap_extension_not_installed' => 'Lextensió PHP de lLDAP no està instal·lada',
'ldap_cannot_connect' => 'No sha pogut connectar amb el servidor LDAP perquè la connexió inicial ha fallat',
'saml_already_logged_in' => 'Ja heu iniciat sessió',
@@ -29,15 +29,15 @@ return [
'social_no_action_defined' => 'No sha definit cap acció',
'social_login_bad_response' => "Sha produït un error en linici de sessió amb :socialAccount: \n:error",
'social_account_in_use' => 'Aquest compte de :socialAccount ja sestà utilitzant. Proveu diniciar sessió amb :socialAccount.',
'social_account_email_in_use' => 'Ladreça electrònica :email ja està en ús. Si ja teniu un compte, podeu connectar-hi el vostre compte de :socialAccount a la configuració del vostre perfil.',
'social_account_email_in_use' => 'Ladreça electrònica :email ja sestà utilitzant. Si ja teniu uns compte podeu connectar-hi el vostre compte de :socialAccount des de la configuració del vostre perfil.',
'social_account_existing' => 'Aquest compte de :socialAccount ja està associat al vostre perfil.',
'social_account_already_used_existing' => 'Aquest compte de :socialAccount ja està associat a un altre usuari.',
'social_account_not_used' => 'Aquest compte de :socialAccount no està associat a cap usuari. Associeu-lo a la configuració del vostre perfil. ',
'social_account_register_instructions' => 'Si encara no teniu un compte, podeu registrar-vos amb lopció :socialAccount.',
'social_driver_not_found' => 'No sha trobat el controlador social',
'social_driver_not_configured' => 'La configuració de :socialAccount no és correcta.',
'invite_token_expired' => 'Aquest enllaç dinvitació ha caducat. Podeu provar de restablir la contrasenya del vostre compte.',
'login_user_not_found' => 'No s\'ha pogut trobar un usuari per aquesta acció.',
'invite_token_expired' => 'Aquest enllaç dinvitació ha caducat. Proveu de reinicialitzar la contrasenya.',
'login_user_not_found' => 'A user for this action could not be found.',
// System
'path_not_writable' => 'No sha pogut pujar a :filePath. Assegureu-vos que teniu permisos descriptura al servidor.',
@@ -78,7 +78,7 @@ return [
// Users
'users_cannot_delete_only_admin' => 'No podeu suprimir ladministrador únic.',
'users_cannot_delete_guest' => 'No podeu suprimir lusuari convidat.',
'users_could_not_send_invite' => 'No s\'ha pogut crear l\'usuari, ja que no s\'ha pogut enviar el correu d\'invitació',
'users_could_not_send_invite' => 'Could not create user since invite email failed to send',
// Roles
'role_cannot_be_edited' => 'No es pot editar aquest rol.',
@@ -106,16 +106,16 @@ return [
'back_soon' => 'Aviat ho arreglarem.',
// Import
'import_zip_cant_read' => 'No es pot llegir el fitxer ZIP.',
'import_zip_cant_decode_data' => 'No s\'ha pogut trobar i descodificar el fitxer data.json en el fitxer ZIP.',
'import_zip_no_data' => 'Les dades del fitxer ZIP no contenen cap llibre, capítol o contingut de pàgina.',
'import_validation_failed' => 'Error en validar la importació del ZIP amb els errors:',
'import_zip_failed_notification' => 'Error en importar l\'arxiu ZIP.',
'import_perms_books' => 'Li falten els permisos necessaris per crear llibres.',
'import_perms_chapters' => 'Li falten els permisos necessaris per crear capítols.',
'import_perms_pages' => 'Li falten els permisos necessaris per crear pàgines.',
'import_perms_images' => 'Li falten els permisos necessaris per crear imatges.',
'import_perms_attachments' => 'Li falten els permisos necessaris per crear adjunts.',
'import_zip_cant_read' => 'Could not read ZIP file.',
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
'import_zip_failed_notification' => 'Failed to import ZIP file.',
'import_perms_books' => 'You are lacking the required permissions to create books.',
'import_perms_chapters' => 'You are lacking the required permissions to create chapters.',
'import_perms_pages' => 'You are lacking the required permissions to create pages.',
'import_perms_images' => 'You are lacking the required permissions to create images.',
'import_perms_attachments' => 'You are lacking the required permission to create attachments.',
// API errors
'api_no_authorization_found' => 'No sha trobat cap testimoni dautorització en aquesta sol·licitud.',

View File

@@ -13,7 +13,7 @@ return [
'updated_page_debounce' => 'Per a evitar que sacumulin les notificacions, durant un temps no se us notificarà cap canvi fet en aquesta pàgina pel mateix usuari.',
'detail_page_name' => 'Nom de la pàgina:',
'detail_page_path' => 'Ruta de la pàgina:',
'detail_page_path' => 'Camí de la pàgina:',
'detail_commenter' => 'Autor del comentari:',
'detail_comment' => 'Comentari:',
'detail_created_by' => 'Creada per:',

View File

@@ -8,7 +8,7 @@ return [
// Common Messages
'settings' => 'Configuració',
'settings_save' => 'Guardar configuració',
'settings_save' => 'Configuració de desat',
'system_version' => 'Versió de sistema',
'categories' => 'Categories',
@@ -19,8 +19,8 @@ return [
'app_name_desc' => 'El nom es mostra a la capçalera i als correus electrònics enviats pel sistema.',
'app_name_header' => 'Mostra el nom a la capçalera',
'app_public_access' => 'Accés públic',
'app_public_access_desc' => 'Si activeu aquesta opció permetrà als visitants, accedir a la vostra instància del BookStack sense iniciar sessió.',
'app_public_access_desc_guest' => 'Laccés per als visitants públics es pot gestionar amb lusuari Convidat.',
'app_public_access_desc' => 'Si activeu aquesta opció les visitants podran accedir a la vostra instància del BookStack sense iniciar sessió.',
'app_public_access_desc_guest' => 'Laccés per als visitants públics es pot gestionar amb lusuari &laquo;Convidat&raquo;.',
'app_public_access_toggle' => 'Permet laccés públic',
'app_public_viewing' => 'Esteu segur que voleu permetre laccés públic?',
'app_secure_images' => 'Pujada dimatges amb més seguretat',
@@ -75,34 +75,34 @@ return [
'reg_confirm_restrict_domain_placeholder' => 'No hi ha cap restricció',
// Sorting Settings
'sorting' => 'Ordenar',
'sorting_book_default' => 'Ordre predeterminat del llibre',
'sorting_book_default_desc' => 'Selecciona la regla d\'ordenació predeterminada per aplicar a nous llibres. Això no afectarà els llibres existents, i pot ser anul·lat per llibre.',
'sorting_rules' => 'Regles d\'ordenació',
'sorting_rules_desc' => 'Són operacions d\'ordenació predefinides que es poden aplicar al contingut en el sistema.',
'sort_rule_assigned_to_x_books' => 'Assignat a :count llibre | Assignat a :count llibres',
'sort_rule_create' => 'Crear regla d\'ordenació',
'sort_rule_edit' => 'Editar regla d\'ordenació',
'sort_rule_delete' => 'Eliminar regla d\'ordenació',
'sort_rule_delete_desc' => 'Eliminar aquesta regla d\'ordenació del sistema. Els llibres que utilitzin aquest tipus, es revertiran a l\'ordenació manual.',
'sort_rule_delete_warn_books' => 'Aquesta regla d\'ordenació s\'utilitza actualment en :count llibre(s). Està segur que vol eliminar-la?',
'sort_rule_delete_warn_default' => 'Aquesta regla d\'ordenació s\'utilitza actualment com a predeterminada per als llibres. Està segur que vol eliminar-la?',
'sort_rule_details' => 'Detalls de la regla d\'ordenació',
'sort_rule_details_desc' => 'Defineix un nom per aquesta regla d\'ordenació, que apareixerà a les llistes quan els usuaris estiguin seleccionant un ordre.',
'sort_rule_operations' => 'Operacions d\'ordenació',
'sort_rule_operations_desc' => 'Configura les accions d\'ordenació a realitzar movent-les de la llista d\'operacions disponibles. En utilitzar-se, les operacions s\'aplicaran en ordre, de dalt cap a baix. Qualsevol canvi realitzat aquí, s\'aplicarà a tots els llibres assignats en guardar.',
'sort_rule_available_operations' => 'Operacions disponibles',
'sort_rule_available_operations_empty' => 'No hi ha operacions pendents',
'sort_rule_configured_operations' => 'Operacions configurades',
'sort_rule_configured_operations_empty' => 'Arrossegar/afegir operacions des de la llista d\'Operacions Disponibles',
'sorting' => 'Sorting',
'sorting_book_default' => 'Default Book Sort',
'sorting_book_default_desc' => 'Select the default sort rule to apply to new books. This won\'t affect existing books, and can be overridden per-book.',
'sorting_rules' => 'Sort Rules',
'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.',
'sort_rule_assigned_to_x_books' => 'Assigned to :count Book|Assigned to :count Books',
'sort_rule_create' => 'Create Sort Rule',
'sort_rule_edit' => 'Edit Sort Rule',
'sort_rule_delete' => 'Delete Sort Rule',
'sort_rule_delete_desc' => 'Remove this sort rule from the system. Books using this sort will revert to manual sorting.',
'sort_rule_delete_warn_books' => 'This sort rule is currently used on :count book(s). Are you sure you want to delete this?',
'sort_rule_delete_warn_default' => 'This sort rule is currently used as the default for books. Are you sure you want to delete this?',
'sort_rule_details' => 'Sort Rule Details',
'sort_rule_details_desc' => 'Set a name for this sort rule, which will appear in lists when users are selecting a sort.',
'sort_rule_operations' => 'Sort Operations',
'sort_rule_operations_desc' => 'Configure the sort actions to be performed by moving them from the list of available operations. Upon use, the operations will be applied in order, from top to bottom. Any changes made here will be applied to all assigned books upon save.',
'sort_rule_available_operations' => 'Available Operations',
'sort_rule_available_operations_empty' => 'No operations remaining',
'sort_rule_configured_operations' => 'Configured Operations',
'sort_rule_configured_operations_empty' => 'Drag/add operations from the "Available Operations" list',
'sort_rule_op_asc' => '(Asc)',
'sort_rule_op_desc' => '(Desc)',
'sort_rule_op_name' => 'Nom - Alfabètic',
'sort_rule_op_name_numeric' => 'Nom - Numèric',
'sort_rule_op_created_date' => 'Data de creació',
'sort_rule_op_updated_date' => 'Data d\'actualització',
'sort_rule_op_chapters_first' => 'Capítols a l\'inici',
'sort_rule_op_chapters_last' => 'Capítols al final',
'sort_rule_op_name' => 'Name - Alphabetical',
'sort_rule_op_name_numeric' => 'Name - Numeric',
'sort_rule_op_created_date' => 'Created Date',
'sort_rule_op_updated_date' => 'Updated Date',
'sort_rule_op_chapters_first' => 'Chapters First',
'sort_rule_op_chapters_last' => 'Chapters Last',
// Maintenance settings
'maint' => 'Manteniment',
@@ -139,7 +139,7 @@ return [
'recycle_bin_contents_empty' => 'La paperera és buida',
'recycle_bin_empty' => 'Buida la paperera',
'recycle_bin_empty_confirm' => 'Se suprimiran permanentment tots els elements que hi ha a la paperera incloent-hi el contingut que hi hagi a cada element. Esteu segur que voleu buidar la paperera?',
'recycle_bin_destroy_confirm' => 'Aquesta acció suprimirà del sistema de manera permanent aquest element, juntament amb tots els fills que es llisten a sota, i no podreu restaurar aquest contingut. Segur que voleu suprimir de manera permanent aquest element?',
'recycle_bin_destroy_confirm' => 'This action will permanently delete this item from the system, along with any child elements listed below, and you will not be able to restore this content. Are you sure you want to permanently delete this item?',
'recycle_bin_destroy_list' => 'Elements per destruir',
'recycle_bin_restore_list' => 'Elements per restaurar',
'recycle_bin_restore_confirm' => 'Aquesta acció restaurarà lelement suprimit, incloent-hi els elements fills, a la seva ubicació original. Si la ubicació original sha suprimit i és a la paperera, lelement pare també shaurà de restaurar.',
@@ -192,7 +192,7 @@ return [
'role_access_api' => 'Accés a lAPI del sistema',
'role_manage_settings' => 'Gestió de la configuració de laplicació',
'role_export_content' => 'Exportació de contingut',
'role_import_content' => 'Importar contingut',
'role_import_content' => 'Import content',
'role_editor_change' => 'Canvi de leditor de pàgina',
'role_notifications' => 'Recepció i gestió de notificacions',
'role_asset' => 'Permisos de recursos',
@@ -308,13 +308,13 @@ return [
'webhooks_last_error_message' => 'Darrer missatge derror:',
// Licensing
'licenses' => 'Llicències',
'licenses_desc' => 'Aquesta pàgina detalla informació sobre la llicència de BookStack a més dels projectes i biblioteques que s\'utilitzen en BookStack. Molts projectes enumerats aquí poden ser utilitzats només en un context de desenvolupament.',
'licenses_bookstack' => 'Llicència de BookStack',
'licenses_php' => 'Llicències de Biblioteques de PHP',
'licenses_js' => 'Llicències de Biblioteques de JavaScript',
'licenses_other' => 'Altres llicències',
'license_details' => 'Detalls de la llicència',
'licenses' => 'Licenses',
'licenses_desc' => 'This page details license information for BookStack in addition to the projects & libraries that are used within BookStack. Many projects listed may only be used in a development context.',
'licenses_bookstack' => 'BookStack License',
'licenses_php' => 'PHP Library Licenses',
'licenses_js' => 'JavaScript Library Licenses',
'licenses_other' => 'Other Licenses',
'license_details' => 'License Details',
//! If editing translations files directly please ignore this in all
//! languages apart from en. Content will be auto-copied from en.
@@ -348,7 +348,6 @@ return [
'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nb' => 'Norsk (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Nederlands',
'pl' => 'Polski',

View File

@@ -105,10 +105,10 @@ return [
'url' => 'El format :attribute no és vàlid.',
'uploaded' => 'No sha pogut pujar el fitxer. És possible que el servidor no admeti fitxers daquesta mida.',
'zip_file' => 'El :attribute necessita fer referència a un arxiu dins del ZIP.',
'zip_file_mime' => 'El :attribute necessita fer referència a un arxiu de tipus :validTyes, trobat :foundType.',
'zip_model_expected' => 'S\'esperava un objecte de dades, però s\'ha trobat ":type".',
'zip_unique' => 'El :attribute ha de ser únic pel tipus d\'objecte dins del ZIP.',
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
'zip_model_expected' => 'Data object expected but ":type" found.',
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
// Custom validation lines
'custom' => [

View File

@@ -47,8 +47,7 @@ return [
'strikethrough' => 'Proškrtnuté',
'superscript' => 'horní index',
'subscript' => 'Dolní index',
'text_color' => 'Barva textu',
'highlight_color' => 'Barva zvýraznění',
'text_color' => 'Barva textu:',
'custom_color' => 'Vlastní barva',
'remove_color' => 'Odstranit barvu',
'background_color' => 'Barva pozadí',

View File

@@ -63,10 +63,6 @@ return [
'import_delete_desc' => 'Potvrzením odstraníte nahraný ZIP soubor. Tento krok nelze vrátit zpět.',
'import_errors' => 'Chyby importu',
'import_errors_desc' => 'Při pokusu o import došlo k následujícím chybám:',
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
// Permissions and restrictions
'permissions' => 'Oprávnění',
@@ -138,7 +134,7 @@ return [
'shelves_copy_permissions_to_books' => 'Kopírovat oprávnění na knihy',
'shelves_copy_permissions' => 'Kopírovat oprávnění',
'shelves_copy_permissions_explain' => 'Tímto se použije aktuální nastavení oprávnění police na všechny knihy v ní obsažené. Před aktivací se ujistěte, že byly uloženy všechny změny oprávnění této police.',
'shelves_copy_permission_success' => '{1}Oprávnění police byla zkopírována na :count knihu|[2,4]Oprávnění police byla zkopírována na :count knihy|[5,*]Oprávnění police byla zkopírována na :count knih',
'shelves_copy_permission_success' => 'Oprávnění police byla zkopírována na :count knih',
// Books
'book' => 'Kniha',
@@ -198,7 +194,7 @@ return [
// Chapters
'chapter' => 'Kapitola',
'chapters' => 'Kapitoly',
'x_chapters' => '{0}:count kapitol|{1}:count kapitola|[2,4]:count kapitoly|[5,*]:count kapitol',
'x_chapters' => '{0}:count Kapitol|{1}:count Kapitola|[2,4]:count Kapitoly|[5,*]:count Kapitol',
'chapters_popular' => 'Populární kapitoly',
'chapters_new' => 'Nová kapitola',
'chapters_create' => 'Vytvořit novou kapitolu',
@@ -223,7 +219,7 @@ return [
// Pages
'page' => 'Stránka',
'pages' => 'Stránky',
'x_pages' => '{0}:count stran|{1}:count strana|[2,4]:count strany|[5,*]:count stran',
'x_pages' => '{0}:count Stran|{1}:count Strana|[2,4]:count Strany|[5,*]:count Stran',
'pages_popular' => 'Populární stránky',
'pages_new' => 'Nová stránka',
'pages_attachments' => 'Přílohy',
@@ -248,8 +244,8 @@ return [
'pages_edit_delete_draft_confirm' => 'Jste si jisti, že chcete odstranit změny vašich konceptů? Všechny vaše změny, od posledního úplného uložení, budou ztraceny a editor bude aktualizován s nejnovějším stavem nekonceptu stránky.',
'pages_edit_discard_draft' => 'Zahodit koncept',
'pages_edit_switch_to_markdown' => 'Přepnout na Markdown Editor',
'pages_edit_switch_to_markdown_clean' => '(Prostý zápis)',
'pages_edit_switch_to_markdown_stable' => '(Formátovaný zápis)',
'pages_edit_switch_to_markdown_clean' => '(Vytvořený obsah)',
'pages_edit_switch_to_markdown_stable' => '(Stabilní obsah)',
'pages_edit_switch_to_wysiwyg' => 'Přepnout na WYSIWYG Editor',
'pages_edit_switch_to_new_wysiwyg' => 'Přepnout na nový WYSIWYG',
'pages_edit_switch_to_new_wysiwyg_desc' => '(V beta testování)',
@@ -272,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Vložit kresbu',
'pages_md_show_preview' => 'Zobrazit náhled',
'pages_md_sync_scroll' => 'Synchronizovat náhled',
'pages_md_plain_editor' => 'Upravovat prostý text',
'pages_drawing_unsaved' => 'Nalezen neuložený výkres',
'pages_drawing_unsaved_confirm' => 'Byly nalezeny neuložené kresby z předchozí neúspěšné pokusu o uložení kresby. Chcete je obnovit a pokračovat v úpravě této neuložené kresby?',
'pages_not_in_chapter' => 'Stránka není v kapitole',
@@ -317,7 +312,7 @@ return [
'pages_draft_edited_notification' => 'Tato stránka se od té doby změnila. Je doporučeno aktuální koncept zahodit.',
'pages_draft_page_changed_since_creation' => 'Tato stránka byla aktualizována od vytvoření tohoto konceptu. Doporučuje se zrušit tento koncept nebo se postarat o to, abyste si nepřepsali žádné již zadané změny.',
'pages_draft_edit_active' => [
'start_a' => '{1}:count uživatel začal upravovat tuto stránku|[2,4]:count uživatelé začali upravovat tuto stránku|[5,*]:count uživatelů začalo upravovat tuto stránku',
'start_a' => 'Uživatelé začali upravovat tuto stránku (celkem :count)',
'start_b' => ':userName začal/a upravovat tuto stránku',
'time_a' => 'od doby, kdy byla tato stránky naposledy aktualizována',
'time_b' => 'v posledních minutách (:minCount min.)',
@@ -347,7 +342,7 @@ return [
'tags_assigned_chapters' => 'Přiřazeno ke kapitolám',
'tags_assigned_books' => 'Přiřazeno ke knihám',
'tags_assigned_shelves' => 'Přiřazeno k policím',
'tags_x_unique_values' => '{1}:count jedinečná hodnota|[2,4]:count jedinečné hodnoty|[5,*]:count jedinečných hodnot',
'tags_x_unique_values' => ':count jedinečných hodnot',
'tags_all_values' => 'Všechny hodnoty',
'tags_view_tags' => 'Zobrazit štítky',
'tags_view_existing_tags' => 'Zobrazit existující štítky',
@@ -399,8 +394,8 @@ return [
'comment_add' => 'Přidat komentář',
'comment_none' => 'Žádné komentáře k zobrazení',
'comment_placeholder' => 'Zde zadejte komentář',
'comment_thread_count' => '{0}:count vláken komentářů|{1}:count vlákno komentářů|[2,4]:count vlákna komentářů|[5,*]:count vláken komentářů',
'comment_archived_count' => '[0,1]:count archivováno|[2,4]:count archivována|[5,*]:count archivováno',
'comment_thread_count' => ':count vlákno komentáře|:count vláken komentářů',
'comment_archived_count' => ':count archivováno',
'comment_archived_threads' => 'Archivovaná vlákna',
'comment_save' => 'Uložit komentář',
'comment_new' => 'Nový komentář',

View File

@@ -348,7 +348,6 @@ return [
'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nb' => 'Norsk (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Nederlands',
'pl' => 'Polski',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Uwchysgrif',
'subscript' => 'Isysgrif',
'text_color' => 'Lliw testun',
'highlight_color' => 'Highlight color',
'custom_color' => 'Lliw addasu',
'remove_color' => 'Dileu lliw',
'background_color' => 'Lliw cefnder',

View File

@@ -63,10 +63,6 @@ return [
'import_delete_desc' => 'Bydd hwn yn dileu\'r mewnforyn ffeil ZIP sy wedi\'i lwytho i fyny, a fydd e ddim gallu cael ei ddadwneud.',
'import_errors' => 'Gwallau Mewnforyn',
'import_errors_desc' => 'Digwyddodd y gwallau canlynol yn ystod cynnig y mewnforyn:',
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
// Permissions and restrictions
'permissions' => 'Caniatâd',
@@ -272,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Mewnosod Llun',
'pages_md_show_preview' => 'Dangos rhagolwg',
'pages_md_sync_scroll' => 'Cydamseru sgrôl ragolwg',
'pages_md_plain_editor' => 'Plaintext editor',
'pages_drawing_unsaved' => 'Canfuwyd Llun heb ei Gadw',
'pages_drawing_unsaved_confirm' => 'Canfuwyd data llun heb ei gadw o ymgais aflwyddiannus blaenorol i gadw llun. Hoffech chi adfer a pharhau i olygu\'r llun heb ei gadw?',
'pages_not_in_chapter' => 'Nid yw\'r dudalen mewn pennod',

View File

@@ -348,7 +348,6 @@ return [
'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nb' => 'Norsk (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Nederlands',
'pl' => 'Polski',

View File

@@ -6,11 +6,11 @@
*/
return [
'failed' => 'De indtastede brugeroplysninger stemmer ikke overens med vores registreringer.',
'failed' => 'Dee indtastede brugeroplysninger stemmer ikke overens med vores registreringer.',
'throttle' => 'For mange mislykkede loginforsøg. Prøv igen om :seconds sekunder.',
// Login & Register
'sign_up' => 'Registrer',
'sign_up' => 'Registrér',
'log_in' => 'Log ind',
'log_in_with' => 'Log ind med :socialDriver',
'sign_up_with' => 'Registrér med :socialDriver',
@@ -45,28 +45,28 @@ return [
// Password Reset
'reset_password' => 'Nulstil adgangskode',
'reset_password_send_instructions' => 'Indtast din e-mail herunder og du vil blive sendt en e-mail med et link til at nulstille din adgangskode.',
'reset_password_send_instructions' => 'Indtast din E-Mail herunder og du vil blive sendt en E-Mail med et link til at nulstille din adgangskode.',
'reset_password_send_button' => 'Send link til nulstilling',
'reset_password_sent' => 'Et link til nulstilling af adgangskode sendes til :email, hvis den e-mail-adresse findes i systemet.',
'reset_password_success' => 'Din adgangskode er blevet nulstillet.',
'email_reset_subject' => 'Nulstil din :appName adgangskode',
'email_reset_text' => 'Du modtager denne e-mail fordi vi har modtaget en anmodning om at nulstille din adgangskode.',
'email_reset_text' => 'Du modtager denne E-Mail fordi vi har modtaget en anmodning om at nulstille din adgangskode.',
'email_reset_not_requested' => 'Hvis du ikke har anmodet om at få din adgangskode nulstillet, behøver du ikke at foretage dig noget.',
// Email Confirmation
'email_confirm_subject' => 'Bekræft din E-Mail på :appName',
'email_confirm_greeting' => 'Tak for at oprette dig på :appName!',
'email_confirm_text' => 'Bekræft venligst din e-mail adresse ved at klikke på linket nedenfor:',
'email_confirm_action' => 'Bekræft e-mail',
'email_confirm_send_error' => 'E-mailbekræftelse kræves, men systemet kunne ikke sende e-mailen. Kontakt administratoren for at sikre, at e-mail er konfigureret korrekt.',
'email_confirm_success' => 'Din e-mail er blevet bekræftet! Du bør nu kunne logge ind med denne e-mailadresse.',
'email_confirm_text' => 'Bekræft venligst din E-Mail adresse ved at klikke på linket nedenfor:',
'email_confirm_action' => 'Bekræft E-Mail',
'email_confirm_send_error' => 'E-Mail-bekræftelse kræves, men systemet kunne ikke sende E-Mailen. Kontakt administratoren for at sikre, at E-Mail er konfigureret korrekt.',
'email_confirm_success' => 'Din email er blevet bekræftet! Du bør nu kune logge ind med denne emailadresse.',
'email_confirm_resent' => 'Bekræftelsesmail sendt, tjek venligst din indboks.',
'email_confirm_thanks' => 'Tak for bekræftelsen!',
'email_confirm_thanks_desc' => 'Vent venligst et øjeblik, mens din bekræftelse behandles. Hvis du ikke bliver omdirigeret efter 3 sekunder, skal du trykke på linket "Fortsæt" nedenfor for at fortsætte.',
'email_not_confirmed' => 'E-mailadresse ikke bekræftet',
'email_not_confirmed_text' => 'Din e-mailadresse er endnu ikke blevet bekræftet.',
'email_not_confirmed_click_link' => 'Klik venligst på linket i e-mailen der blev sendt kort efter du registrerede dig.',
'email_not_confirmed' => 'E-Mail adresse ikke bekræftet',
'email_not_confirmed_text' => 'Din E-Mail adresse er endnu ikke blevet bekræftet.',
'email_not_confirmed_click_link' => 'Klik venligst på linket i E-Mailen der blev sendt kort efter du registrerede dig.',
'email_not_confirmed_resend' => 'Hvis du ikke kan finde E-Mailen, kan du du få gensendt bekræftelsesemailen ved at trykke herunder.',
'email_not_confirmed_resend_button' => 'Gensend bekræftelsesemail',

View File

@@ -30,8 +30,8 @@ return [
'create' => 'Opret',
'update' => 'Opdater',
'edit' => 'Rediger',
'archive' => 'Arkiver',
'unarchive' => 'Tilbagekald',
'archive' => 'Archive',
'unarchive' => 'Un-Archive',
'sort' => 'Sorter',
'move' => 'Flyt',
'copy' => 'Kopier',
@@ -50,8 +50,8 @@ return [
'unfavourite' => 'Fjern som foretrukken',
'next' => 'Næste',
'previous' => 'Forrige',
'filter_active' => 'Aktivt filter:',
'filter_clear' => 'Nulstil filter',
'filter_active' => 'Aktivt Filter:',
'filter_clear' => 'Nulstil Filter',
'download' => 'Hent',
'open_in_tab' => 'Åben i ny fane',
'open' => 'Åbn',

View File

@@ -6,8 +6,8 @@ return [
// Image Manager
'image_select' => 'Billedselektion',
'image_list' => 'Billedliste',
'image_details' => 'Billeddetaljer',
'image_list' => 'Billede Liste',
'image_details' => 'Billede Detaljer',
'image_upload' => 'Upload billede',
'image_intro' => 'Her kan du vælge og administrere billeder, der tidligere er blevet uploadet til systemet.',
'image_intro_upload' => 'Upload et nyt billede ved at trække en billedfil ind i dette vindue, eller ved at bruge knappen "Upload billede" ovenfor.',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Hævet',
'subscript' => 'Sænket',
'text_color' => 'Tekstfarve',
'highlight_color' => 'Highlight color',
'custom_color' => 'Tilpasset farve',
'remove_color' => 'Fjern farve',
'background_color' => 'Baggrundsfarve',

View File

@@ -63,10 +63,6 @@ return [
'import_delete_desc' => 'Dette vil slette den uploadede ZIP-fil og kan ikke fortrydes.',
'import_errors' => 'Importfejl',
'import_errors_desc' => 'Følgende fejl opstod under importforsøget:',
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
// Permissions and restrictions
'permissions' => 'Rettigheder',
@@ -272,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Indsæt tegning',
'pages_md_show_preview' => 'Vis forhåndsvisning',
'pages_md_sync_scroll' => 'Rulning af forhåndsvisning af synkronisering',
'pages_md_plain_editor' => 'Klartekst editor',
'pages_drawing_unsaved' => 'Ikke gemt tegning fundet',
'pages_drawing_unsaved_confirm' => 'Der blev fundet ikke-gemte tegningsdata fra et tidligere mislykket forsøg på at gemme en tegning. Vil du gendanne og fortsætte med at redigere denne ikke-gemte tegning?',
'pages_not_in_chapter' => 'Side er ikke i et kapitel',
@@ -400,7 +395,7 @@ return [
'comment_none' => 'No comments to display',
'comment_placeholder' => 'Skriv en kommentar her',
'comment_thread_count' => ':count Comment Thread|:count Comment Threads',
'comment_archived_count' => ':count Arkiveret',
'comment_archived_count' => ':count Archived',
'comment_archived_threads' => 'Archived Threads',
'comment_save' => 'Gem kommentar',
'comment_new' => 'Ny kommentar',
@@ -412,12 +407,12 @@ return [
'comment_updated_success' => 'Kommentaren er opdateret',
'comment_archive_success' => 'Comment archived',
'comment_unarchive_success' => 'Comment un-archived',
'comment_view' => 'Se kommentar',
'comment_jump_to_thread' => 'Hop til tråd',
'comment_view' => 'View comment',
'comment_jump_to_thread' => 'Jump to thread',
'comment_delete_confirm' => 'Er du sikker på, at du vil slette denne kommentar?',
'comment_in_reply_to' => 'Som svar til :commentId',
'comment_reference' => 'Reference',
'comment_reference_outdated' => '(Forældet)',
'comment_reference_outdated' => '(Outdated)',
'comment_editor_explain' => 'Her er de kommentarer, der er blevet efterladt på denne side. Kommentarer kan tilføjes og administreres, når du ser den gemte side.',
// Revision

View File

@@ -7,7 +7,7 @@
return [
'password' => 'Adgangskoder skal være mindst otte tegn og svare til bekræftelsen.',
'user' => "Vi kan ikke finde en bruger med den e-mailadresse.",
'user' => "Vi kan ikke finde en bruger med den e-mail adresse.",
'token' => 'Linket til nulstilling af adgangskode er ugyldigt for denne e-mail-adresse.',
'sent' => 'Vi har sendt dig en e-mail med et link til at nulstille adgangskoden!',
'reset' => 'Dit kodeord er blevet nulstillet!',

View File

@@ -24,12 +24,12 @@ return [
'notifications_opt_own_page_changes' => 'Adviser ved ændringer af sider, jeg ejer',
'notifications_opt_own_page_comments' => 'Adviser ved kommentarer på sider, jeg ejer',
'notifications_opt_comment_replies' => 'Adviser ved svar på mine kommentarer',
'notifications_save' => 'Gem indstillinger',
'notifications_save' => 'Gem Indstillinger',
'notifications_update_success' => 'Indstillinger for notifikationer er blevet opdateret!',
'notifications_watched' => 'Overvågede og ignorerede',
'notifications_watched' => 'Overvågede & Ignorerede',
'notifications_watched_desc' => 'Nedenfor er de elementer, der har brugerdefinerede overvågning aktivt. For at opdatere dine præferencer for disse, gå til elementet og find derefter overvågning i sidepanelet.',
'auth' => 'Adgang og sikkerhed',
'auth' => 'Adgang & Sikkerhed',
'auth_change_password' => 'Skift adgangskode',
'auth_change_password_desc' => 'Skift den adgangskode, du bruger til at logge ind med. Den skal være mindst 8 tegn lang.',
'auth_change_password_success' => 'Adgangskoden er blevet opdateret!',
@@ -41,7 +41,7 @@ return [
'profile_email_desc' => 'Denne e-mail vil blive brugt til notifikationer og, afhængigt af aktiv systemgodkendelse, systemadgang.',
'profile_email_no_permission' => 'Desværre har du ikke tilladelse til at ændre din e-mailadresse. Hvis du ønsker at ændre dette, skal du bede en administrator om at ændre dette for dig.',
'profile_avatar_desc' => 'Vælg et billede som vil blive brugt til at repræsentere dig selv over for andre i systemet. Ideelt set bør dette billede være kvadrat og omkring 256px i bredde og højde.',
'profile_admin_options' => 'Administratorindstillinger',
'profile_admin_options' => 'Administrator Indstillinger',
'profile_admin_options_desc' => 'Yderligere indstillinger på administratorniveau, såsom dem der håndterer rolleopgaver, kan findes for din brugerkonto i området "Indstillinger > Brugere".',
'delete_account' => 'Slet konto',

View File

@@ -26,14 +26,14 @@ return [
'app_secure_images' => 'Højere sikkerhed for billeduploads',
'app_secure_images_toggle' => 'Aktiver højere sikkerhed for billeduploads',
'app_secure_images_desc' => 'Af performanceårsager er alle billeder offentlige. Denne funktion tilføjer en tilfældig, vanskelig at gætte streng foran billed-url\'er. Sørg for, at mappeindeksering ikke er aktiveret for at forhindre nem adgang.',
'app_default_editor' => 'Standard sideeditor',
'app_default_editor' => 'Standard Side Editor',
'app_default_editor_desc' => 'Vælg hvilken editor der som standard skal bruges ved redigering af nye sider. Dette kan tilsidesættes på side niveau, hvor tilladelser tillader det.',
'app_custom_html' => 'Tilpasset HTML head indhold',
'app_custom_html_desc' => 'Alt indhold tilføjet her, vil blive indsat i bunden af <head> sektionen på alle sider. Dette er brugbart til overskrivning af styles og tilføjelse af analytics kode.',
'app_custom_html_disabled_notice' => 'Brugerdefineret HTML head indhold er deaktiveret på denne indstillingsside for at, at ændringer kan rulles tilbage.',
'app_logo' => 'Applikationslogo',
'app_logo_desc' => 'Det bruges blandt andet i applikationens headerbar. Dette billede skal være 86px i højden. Store billeder vil blive skaleret ned.',
'app_icon' => 'Programikon',
'app_icon' => 'Program ikon',
'app_icon_desc' => 'Dette ikon bruges til browserfaner og genvejsikoner. Det skal være et 256px kvadratisk PNG-billede.',
'app_homepage' => 'Applikationsforside',
'app_homepage_desc' => 'Vælg en visning, der skal vises på forsiden i stedet for standardvisningen. Sidetilladelser ignoreres for de valgte sider.',
@@ -348,7 +348,6 @@ return [
'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nb' => 'Norsk (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Nederlands',
'pl' => 'Polski',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Hochgestellt',
'subscript' => 'Tiefgestellt',
'text_color' => 'Schriftfarbe',
'highlight_color' => 'Markierungsfarbe',
'custom_color' => 'Benutzerdefinierte Farbe',
'remove_color' => 'Farbe entfernen',
'background_color' => 'Hintergrundfarbe',

View File

@@ -63,10 +63,6 @@ return [
'import_delete_desc' => 'Dies löscht die hochgeladene ZIP-Datei und kann nicht rückgängig gemacht werden.',
'import_errors' => 'Importfehler',
'import_errors_desc' => 'Die folgenden Fehler sind während des Importversuchs aufgetreten:',
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
// Permissions and restrictions
'permissions' => 'Berechtigungen',
@@ -272,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Zeichnung einfügen',
'pages_md_show_preview' => 'Vorschau anzeigen',
'pages_md_sync_scroll' => 'Vorschau synchronisieren',
'pages_md_plain_editor' => 'Plaintext editor',
'pages_drawing_unsaved' => 'Ungespeicherte Zeichnung gefunden',
'pages_drawing_unsaved_confirm' => 'Es wurden ungespeicherte Zeichnungsdaten von einem früheren, fehlgeschlagenen Versuch, die Zeichnung zu speichern, gefunden. Möchten Sie diese ungespeicherte Zeichnung wiederherstellen und weiter bearbeiten?',
'pages_not_in_chapter' => 'Seite ist in keinem Kapitel',

View File

@@ -349,7 +349,6 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung
'lt' => 'Litauisch',
'lv' => 'Lettisch',
'nb' => 'Norwegisch (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Niederländisch',
'pl' => 'Polnisch',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Hochgestellt',
'subscript' => 'Tiefgestellt',
'text_color' => 'Textfarbe',
'highlight_color' => 'Markierungsfarbe',
'custom_color' => 'Benutzerdefinierte Farbe',
'remove_color' => 'Farbe entfernen',
'background_color' => 'Hintergrundfarbe',

View File

@@ -63,10 +63,6 @@ return [
'import_delete_desc' => 'Dies löscht die hochgeladene ZIP-Datei und kann nicht rückgängig gemacht werden.',
'import_errors' => 'Importfehler',
'import_errors_desc' => 'Die folgenden Fehler sind während des Importversuchs aufgetreten:',
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
// Permissions and restrictions
'permissions' => 'Berechtigungen',
@@ -272,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Zeichnung einfügen',
'pages_md_show_preview' => 'Vorschau anzeigen',
'pages_md_sync_scroll' => 'Vorschau synchronisieren',
'pages_md_plain_editor' => 'Plaintext editor',
'pages_drawing_unsaved' => 'Ungespeicherte Zeichnung gefunden',
'pages_drawing_unsaved_confirm' => 'Es wurden ungespeicherte Zeichnungsdaten von einem früheren, fehlgeschlagenen Versuch, die Zeichnung zu speichern, gefunden. Möchtest du diese ungespeicherte Zeichnung wiederherstellen und weiter bearbeiten?',
'pages_not_in_chapter' => 'Seite ist in keinem Kapitel',

View File

@@ -349,7 +349,6 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung
'lt' => 'Litauisch',
'lv' => 'Lettisch',
'nb' => 'Norwegisch (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Niederländisch',
'pl' => 'Polnisch',

Some files were not shown because too many files have changed in this diff Show More