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
345 changed files with 2968 additions and 6434 deletions

View File

@@ -489,14 +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

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

@@ -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

@@ -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

@@ -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

@@ -9,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'],

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

@@ -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;
}
/**

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

@@ -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

@@ -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

@@ -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",

523
composer.lock generated

File diff suppressed because it is too large Load Diff

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 @@
ecb1c038267dde9336e703e14a89d43848880ebb630465e313b4726570f2db04
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

@@ -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

@@ -248,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' => 'أدخل سجل التعديل',
@@ -268,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

@@ -268,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

@@ -268,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

@@ -268,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' => 'Superíndex',
'subscript' => 'Subíndex',
'text_color' => 'Color del text',
'highlight_color' => 'Highlight color',
'custom_color' => 'Color personalitzat',
'remove_color' => 'Elimina el color',
'background_color' => 'Color de fons',

View File

@@ -268,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' => 'Plaintext editor',
'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',

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' => 'horní index',
'subscript' => 'Dolní index',
'text_color' => 'Barva textu:',
'highlight_color' => 'Highlight color',
'custom_color' => 'Vlastní barva',
'remove_color' => 'Odstranit barvu',
'background_color' => 'Barva pozadí',

View File

@@ -134,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',
@@ -194,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',
@@ -219,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',
@@ -268,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' => 'Plaintext editor',
'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',
@@ -313,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.)',
@@ -343,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',
@@ -395,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

@@ -268,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

@@ -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

@@ -268,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' => 'Plaintext 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',

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' => 'Hochgestellt',
'subscript' => 'Tiefgestellt',
'text_color' => 'Schriftfarbe',
'highlight_color' => 'Highlight color',
'custom_color' => 'Benutzerdefinierte Farbe',
'remove_color' => 'Farbe entfernen',
'background_color' => 'Hintergrundfarbe',

View File

@@ -268,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' => 'Highlight color',
'custom_color' => 'Benutzerdefinierte Farbe',
'remove_color' => 'Farbe entfernen',
'background_color' => 'Hintergrundfarbe',

View File

@@ -268,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',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Εκθέτης',
'subscript' => 'Δείκτης',
'text_color' => 'Χρώμα κειμένου',
'highlight_color' => 'Highlight color',
'custom_color' => 'Προσαρμογή χρώματος',
'remove_color' => 'Αφαίρεση χρώματος',
'background_color' => 'Χρώμα φόντου',

View File

@@ -268,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Εισαγωγή Σχεδίου',
'pages_md_show_preview' => 'Εμφάνιση προεπισκόπησης',
'pages_md_sync_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

@@ -268,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' => 'Superíndice',
'subscript' => 'Subíndice',
'text_color' => 'Color de texto',
'highlight_color' => 'Highlight color',
'custom_color' => 'Color personalizado',
'remove_color' => 'Eliminar color',
'background_color' => 'Color de fondo',

View File

@@ -268,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Insertar Dibujo',
'pages_md_show_preview' => 'Mostrar vista previa',
'pages_md_sync_scroll' => 'Sincronizar desplazamiento de vista previa',
'pages_md_plain_editor' => 'Plaintext editor',
'pages_drawing_unsaved' => 'Encontrado dibujo sin guardar',
'pages_drawing_unsaved_confirm' => 'Se encontraron datos no guardados del dibujo de un intento de guardado fallido. ¿Desea restaurar y continuar editando el dibujo no guardado?',
'pages_not_in_chapter' => 'La página no está en un capítulo',

View File

@@ -348,7 +348,6 @@ return [
'lt' => 'Lituano',
'lv' => 'Letón',
'nb' => 'Norsk (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Holanda',
'pl' => 'Polaco',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Superíndice',
'subscript' => 'Subíndice',
'text_color' => 'Color del texto',
'highlight_color' => 'Highlight color',
'custom_color' => 'Color personalizado',
'remove_color' => 'Eliminar color',
'background_color' => 'Color de fondo',

View File

@@ -268,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Insertar Dibujo',
'pages_md_show_preview' => 'Mostrar vista previa',
'pages_md_sync_scroll' => 'Sincronizar desplazamiento de vista previa',
'pages_md_plain_editor' => 'Plaintext editor',
'pages_drawing_unsaved' => 'Encontrado dibujo sin guardar',
'pages_drawing_unsaved_confirm' => 'Se encontraron datos del dibujo no guardados durante un intento de guardado fallido anterior. ¿Desea restaurar y continuar editando el dibujo no guardado?',
'pages_not_in_chapter' => 'La página no esá en el capítulo',

View File

@@ -349,7 +349,6 @@ return [
'lt' => 'Lituano',
'lv' => 'Letón',
'nb' => 'Norsk (Bokmål)',
'ne' => 'नेपाली',
'nn' => 'Nynorsk',
'nl' => 'Holanda',
'pl' => 'Polaco',

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Ülaindeks',
'subscript' => 'Alaindeks',
'text_color' => 'Teksti värv',
'highlight_color' => 'Highlight color',
'custom_color' => 'Kohandatud värv',
'remove_color' => 'Eemalda värv',
'background_color' => 'Taustavärv',

View File

@@ -268,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Lisa joonis',
'pages_md_show_preview' => 'Näita eelvaadet',
'pages_md_sync_scroll' => 'Sünkrooni eelvaate kerimine',
'pages_md_plain_editor' => 'Plaintext editor',
'pages_drawing_unsaved' => 'Leiti salvestamata joonis',
'pages_drawing_unsaved_confirm' => 'Varasemast ebaõnnestunud salvestuskatsest leiti salvestamata joonis. Kas soovid salvestamata joonise taastada ja selle muutmist jätkata?',
'pages_not_in_chapter' => 'Leht ei kuulu peatüki alla',

View File

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

View File

@@ -48,7 +48,6 @@ return [
'superscript' => 'Gain-eskripta',
'subscript' => 'Azpi-script',
'text_color' => 'Testuaren kolorea',
'highlight_color' => 'Highlight color',
'custom_color' => 'Kolore pertsonalizatua',
'remove_color' => 'Kolorea ezabatu',
'background_color' => 'Atzeko planoaren kolorea',

View File

@@ -268,7 +268,6 @@ return [
'pages_md_insert_drawing' => 'Txertatu marrazki berria',
'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

@@ -6,7 +6,7 @@
return [
// Pages
'page_create' => 'صفحه ایجاد شد',
'page_create' => 'تاریخ ایجاد',
'page_create_notification' => 'صفحه با موفقیت ایجاد شد',
'page_update' => 'به روزرسانی صفحه',
'page_update_notification' => 'صفحه با موفقیت به روزرسانی شد',
@@ -85,12 +85,12 @@ return [
'webhook_delete_notification' => 'وب هوک با موفقیت حذف شد',
// Imports
'import_create' => 'ورودی ایجاد شد',
'import_create_notification' => 'فایل با موفقیت آپلود شد',
'import_run' => 'آیتم واردشده بروزرسانی شد',
'import_run_notification' => 'محتوا با موفقیت انتقال یافت',
'import_delete' => 'آیتم ورودی حدف شده',
'import_delete_notification' => 'آیتم واردشده با موفقیت حذف شد',
'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' => 'کاربر ایجاد شده',
@@ -128,12 +128,12 @@ return [
'comment_delete' => 'نظر حذف شده',
// Sort Rules
'sort_rule_create' => 'قانون مرتب‌سازی ایجاد شد',
'sort_rule_create_notification' => 'قانون مرتب‌سازی با موفقیت ایجاد شد',
'sort_rule_update' => 'قانون مرتب‌سازی به‌روزرسانی شد',
'sort_rule_update_notification' => 'قانون مرتب‌سازی با موفقیت به‌روزرسانی شد',
'sort_rule_delete' => 'قانون مرتب‌سازی حذف شد',
'sort_rule_delete_notification' => 'قانون مرتب‌سازی با موفقیت حذف شد',
'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' => 'به روزرسانی مجوزها',

View File

@@ -91,7 +91,7 @@ return [
'mfa_option_totp_title' => 'برنامه ی موبایل',
'mfa_option_totp_desc' => 'برای استفاده از احراز هویت چند عاملی به یک برنامه موبایلی نیاز دارید که از TOTP پشتیبانی کند، مانند Google Authenticator، Authy یا Microsoft Authenticator.',
'mfa_option_backup_codes_title' => 'کدهای پشتیبان',
'mfa_option_backup_codes_desc' => 'این فرایند مجموعه‌ای از کدهای پشتیبان یک‌بار مصرف تولید می‌کند که هنگام ورود به سامانه جهت تأیید هویت باید از آن‌ها استفاده کنید. توصیه می‌شود این کدها را در محلّی امن و محفوظ نگهداری نمایید.',
'mfa_option_backup_codes_desc' => 'Generates a set of one-time-use backup codes which you\'ll enter on login to verify your identity. Make sure to store these in a safe & secure place.',
'mfa_gen_confirm_and_enable' => 'تایید و فعال کنید',
'mfa_gen_backup_codes_title' => 'راه اندازی کدهای پشتیبان',
'mfa_gen_backup_codes_desc' => 'لیست کدهای زیر را در مکانی امن ذخیره کنید. هنگام دسترسی به سیستم، می توانید از یکی از کدها به عنوان مکانیزم احراز هویت دوم استفاده کنید.',

View File

@@ -30,8 +30,8 @@ return [
'create' => 'ایجاد',
'update' => 'به‌روز رسانی',
'edit' => 'ويرايش',
'archive' => 'انتقال به بایگانی',
'unarchive' => 'فعّال‌سازی دوباره (خروج از بایگانی)',
'archive' => 'Archive',
'unarchive' => 'Un-Archive',
'sort' => 'مرتب سازی',
'move' => 'جابجایی',
'copy' => 'کپی',
@@ -111,5 +111,5 @@ return [
'terms_of_service' => 'شرایط خدمات',
// OpenSearch
'opensearch_description' => 'جست‌وجو در :appName',
'opensearch_description' => 'Search :appName',
];

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