mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-08 11:19:36 +03:00
Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ee79d16c9 | ||
|
|
a1ea4006e0 | ||
|
|
a721405202 | ||
|
|
d20aacb732 | ||
|
|
65fa96e405 | ||
|
|
736d6afb7d | ||
|
|
0bcd1795cb | ||
|
|
47887ec595 | ||
|
|
9078188939 | ||
|
|
ed0aad1a7a | ||
|
|
43749cd94e | ||
|
|
107df6c28f | ||
|
|
c1d1ec5b89 | ||
|
|
12c282597d | ||
|
|
c9d0e22132 | ||
|
|
0801955a26 | ||
|
|
3ed1ffdbeb | ||
|
|
5c59cfb020 | ||
|
|
3ca15ad68a | ||
|
|
36f0a68f1b | ||
|
|
ed981cbab1 | ||
|
|
f69af8933c | ||
|
|
46d71a181e | ||
|
|
8d8da31fdd | ||
|
|
0d9b5a9d90 | ||
|
|
8b211ed461 | ||
|
|
9dd69b04b8 | ||
|
|
0c6f598d91 | ||
|
|
df94b73e29 | ||
|
|
7d4b941abf | ||
|
|
d181106df3 | ||
|
|
75110813e6 | ||
|
|
1e41546e51 | ||
|
|
f39b565a1c | ||
|
|
77cd550fae | ||
|
|
96d9077479 | ||
|
|
be1d691529 | ||
|
|
8cde362f6f | ||
|
|
388343aeb0 | ||
|
|
ba25dda031 | ||
|
|
85f59b5275 | ||
|
|
65d4505079 | ||
|
|
663f81a2b1 | ||
|
|
f145ffc930 | ||
|
|
19d7e26dda | ||
|
|
a13b9d8d14 | ||
|
|
8c67011a1d | ||
|
|
8da856bac3 | ||
|
|
90ec40691a | ||
|
|
d676e1e824 | ||
|
|
0a05119aa5 | ||
|
|
abc283fc64 | ||
|
|
e72ade727d | ||
|
|
c8b123bfac | ||
|
|
88012449f3 | ||
|
|
e00d88f45d | ||
|
|
3fe666f36a | ||
|
|
3f271ebecb | ||
|
|
7c597a05f6 | ||
|
|
16e023985d | ||
|
|
43cbab2822 | ||
|
|
1a3505c899 | ||
|
|
2930025f51 | ||
|
|
39fcf3a68f | ||
|
|
6ce34fe6cc | ||
|
|
3c3aed58aa | ||
|
|
73f36b279e | ||
|
|
2b817e7d24 | ||
|
|
cb10ad804f | ||
|
|
eeccc2ef10 | ||
|
|
b030c1398b | ||
|
|
4759fa1e1f | ||
|
|
cb1c2db282 | ||
|
|
4866a3a198 | ||
|
|
340c9ec7a1 | ||
|
|
49498cfaf9 | ||
|
|
3a4aa81115 | ||
|
|
d20c74babf | ||
|
|
9fda0df798 | ||
|
|
6fa699a835 | ||
|
|
78920d7d65 | ||
|
|
35a47a273b | ||
|
|
89dfa43e73 | ||
|
|
2c74dfd1d4 | ||
|
|
e6864a9cff | ||
|
|
60e319c4b4 | ||
|
|
24b31b624c | ||
|
|
a0fe6147d8 | ||
|
|
221d910ff2 | ||
|
|
bef2045df1 | ||
|
|
f021823287 | ||
|
|
60014989f5 | ||
|
|
57b10f195e | ||
|
|
3a8a476906 | ||
|
|
328bc88f02 | ||
|
|
2a99e23e6d | ||
|
|
b855bbaaea | ||
|
|
96436839f1 | ||
|
|
b4f29a85ab | ||
|
|
4a2a044f3d | ||
|
|
ca09ed916f | ||
|
|
dbefda055f | ||
|
|
b1e95eb39f | ||
|
|
b3da77b8f9 | ||
|
|
93ef8c97b6 | ||
|
|
420b29f32f | ||
|
|
d795af04df | ||
|
|
d2ed98d20d | ||
|
|
ebc69a8f2c | ||
|
|
d5ce6b680c | ||
|
|
44013721f0 |
@@ -143,6 +143,10 @@ STORAGE_URL=false
|
||||
# Can be 'standard', 'ldap', 'saml2' or 'oidc'
|
||||
AUTH_METHOD=standard
|
||||
|
||||
# Automatically initiate login via external auth system if it's the only auth method.
|
||||
# Works with saml2 or oidc auth methods.
|
||||
AUTH_AUTO_INITIATE=false
|
||||
|
||||
# Social authentication configuration
|
||||
# All disabled by default.
|
||||
# Refer to https://www.bookstackapp.com/docs/admin/third-party-auth/
|
||||
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,3 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [ssddanbrown]
|
||||
ko_fi: ssddanbrown
|
||||
10
.github/ISSUE_TEMPLATE/config.yml
vendored
10
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,9 +1,13 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discord chat support
|
||||
- name: Discord Chat Support
|
||||
url: https://discord.gg/ztkBqR2
|
||||
about: Realtime support / chat with the community and the team.
|
||||
about: Realtime support & chat with the BookStack community and the team.
|
||||
|
||||
- name: Debugging & Common Issues
|
||||
url: https://www.bookstackapp.com/docs/admin/debugging/
|
||||
about: Find details on how to debug issues and view common issues with thier resolutions.
|
||||
about: Find details on how to debug issues and view common issues with their resolutions.
|
||||
|
||||
- name: Official Support Plans
|
||||
url: https://www.bookstackapp.com/support/
|
||||
about: View our official support plans that offer assured support for business.
|
||||
4
.github/ISSUE_TEMPLATE/language_request.yml
vendored
4
.github/ISSUE_TEMPLATE/language_request.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: Language Request
|
||||
description: Request a new language to be added to CrowdIn for you to translate
|
||||
description: Request a new language to be added to Crowdin for you to translate
|
||||
labels: [":earth_africa: Translations"]
|
||||
assignees:
|
||||
- ssddanbrown
|
||||
@@ -23,7 +23,7 @@ body:
|
||||
This issue template is to request a new language be added to our [Crowdin translation management project](https://crowdin.com/project/bookstack).
|
||||
Please don't use this template to request a new language that you are not prepared to provide translations for.
|
||||
options:
|
||||
- label: I confirm I'm offering to help translate for this new language via CrowdIn.
|
||||
- label: I confirm I'm offering to help translate for this new language via Crowdin.
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
16
.github/translators.txt
vendored
16
.github/translators.txt
vendored
@@ -242,3 +242,19 @@ sgenc :: Turkish
|
||||
Shukrullo (vodiylik) :: Uzbek
|
||||
William W. (Nevnt) :: Chinese Traditional
|
||||
eamaro :: Portuguese
|
||||
Ypsilon-dev :: Arabic
|
||||
Hieu Vuong Trung (vuongtrunghieu) :: Vietnamese
|
||||
David Clubb (davidoclubb) :: Welsh
|
||||
welles freire (wellesximenes) :: Portuguese, Brazilian
|
||||
Magnus Jensen (MagnusHJensen) :: Danish
|
||||
Hesley Magno (hesleymagno) :: Portuguese, Brazilian
|
||||
Éric Gaspar (erga) :: French
|
||||
Fr3shlama :: German
|
||||
DSR :: Spanish, Argentina
|
||||
Andrii Bodnar (andrii-bodnar) :: Ukrainian
|
||||
Younes el Anjri (younesea28) :: Dutch
|
||||
Guclu Ozturk (gucluoz) :: Turkish
|
||||
Atmis :: French
|
||||
redjack666 :: Chinese Traditional
|
||||
Ashita007 :: Russian
|
||||
lihaorr :: Chinese Simplified
|
||||
|
||||
@@ -16,11 +16,13 @@ class ActivityType
|
||||
const CHAPTER_MOVE = 'chapter_move';
|
||||
|
||||
const BOOK_CREATE = 'book_create';
|
||||
const BOOK_CREATE_FROM_CHAPTER = 'book_create_from_chapter';
|
||||
const BOOK_UPDATE = 'book_update';
|
||||
const BOOK_DELETE = 'book_delete';
|
||||
const BOOK_SORT = 'book_sort';
|
||||
|
||||
const BOOKSHELF_CREATE = 'bookshelf_create';
|
||||
const BOOKSHELF_CREATE_FROM_BOOK = 'bookshelf_create_from_book';
|
||||
const BOOKSHELF_UPDATE = 'bookshelf_update';
|
||||
const BOOKSHELF_DELETE = 'bookshelf_delete';
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ class TagRepo
|
||||
'name',
|
||||
($searchTerm || $nameFilter) ? 'value' : DB::raw('COUNT(distinct value) as `values`'),
|
||||
DB::raw('COUNT(id) as usages'),
|
||||
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Page\', 1, 0)) as page_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Chapter\', 1, 0)) as chapter_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Book\', 1, 0)) as book_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'BookStack\\\\BookShelf\', 1, 0)) as shelf_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'page\', 1, 0)) as page_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'chapter\', 1, 0)) as chapter_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'book\', 1, 0)) as book_count'),
|
||||
DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'),
|
||||
])
|
||||
->orderBy($nameFilter ? 'value' : 'name');
|
||||
|
||||
|
||||
@@ -28,10 +28,8 @@ class GroupSyncService
|
||||
*/
|
||||
protected function externalIdMatchesGroupNames(string $externalId, array $groupNames): bool
|
||||
{
|
||||
$externalAuthIds = explode(',', strtolower($externalId));
|
||||
|
||||
foreach ($externalAuthIds as $externalAuthId) {
|
||||
if (in_array(trim($externalAuthId), $groupNames)) {
|
||||
foreach ($this->parseRoleExternalAuthId($externalId) as $externalAuthId) {
|
||||
if (in_array($externalAuthId, $groupNames)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +37,18 @@ class GroupSyncService
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function parseRoleExternalAuthId(string $externalId): array
|
||||
{
|
||||
$inputIds = preg_split('/(?<!\\\),/', strtolower($externalId));
|
||||
$cleanIds = [];
|
||||
|
||||
foreach ($inputIds as $inputId) {
|
||||
$cleanIds[] = str_replace('\,', ',', trim($inputId));
|
||||
}
|
||||
|
||||
return $cleanIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match an array of group names to BookStack system roles.
|
||||
* Formats group names to be lower-case and hyphenated.
|
||||
|
||||
@@ -71,7 +71,7 @@ return [
|
||||
'locale' => env('APP_LANG', 'en'),
|
||||
|
||||
// Locales available
|
||||
'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'de_informal', 'es', 'es_AR', 'et', 'eu', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ru', 'th', 'tr', 'uk', 'uz', 'vi', 'zh_CN', 'zh_TW'],
|
||||
'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'cy', 'da', 'de', 'de_informal', 'es', 'es_AR', 'et', 'eu', 'fa', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ko', 'lt', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl', 'ru', 'th', 'tr', 'uk', 'uz', 'vi', 'zh_CN', 'zh_TW'],
|
||||
|
||||
// Application Fallback Locale
|
||||
'fallback_locale' => 'en',
|
||||
|
||||
@@ -13,6 +13,10 @@ return [
|
||||
// Options: standard, ldap, saml2, oidc
|
||||
'method' => env('AUTH_METHOD', 'standard'),
|
||||
|
||||
// Automatically initiate login via external auth system if it's the sole auth method.
|
||||
// Works with saml2 or oidc auth methods.
|
||||
'auto_initiate' => env('AUTH_AUTO_INITIATE', false),
|
||||
|
||||
// Authentication Defaults
|
||||
// This option controls the default authentication "guard" and password
|
||||
// reset options for your application.
|
||||
|
||||
@@ -72,7 +72,7 @@ return [
|
||||
// to the server if the browser has a HTTPS connection. This will keep
|
||||
// the cookie from being sent to you if it can not be done securely.
|
||||
'secure' => env('SESSION_SECURE_COOKIE', null)
|
||||
?? Str::startsWith(env('APP_URL'), 'https:'),
|
||||
?? Str::startsWith(env('APP_URL', ''), 'https:'),
|
||||
|
||||
// HTTP Access Only
|
||||
// Setting this value to true will prevent JavaScript from accessing the
|
||||
|
||||
@@ -91,6 +91,7 @@ class BookRepo
|
||||
{
|
||||
$book = new Book();
|
||||
$this->baseRepo->create($book, $input);
|
||||
$this->baseRepo->updateCoverImage($book, $input['image'] ?? null);
|
||||
Activity::add(ActivityType::BOOK_CREATE, $book);
|
||||
|
||||
return $book;
|
||||
@@ -102,6 +103,11 @@ class BookRepo
|
||||
public function update(Book $book, array $input): Book
|
||||
{
|
||||
$this->baseRepo->update($book, $input);
|
||||
|
||||
if (array_key_exists('image', $input)) {
|
||||
$this->baseRepo->updateCoverImage($book, $input['image'], $input['image'] === null);
|
||||
}
|
||||
|
||||
Activity::add(ActivityType::BOOK_UPDATE, $book);
|
||||
|
||||
return $book;
|
||||
|
||||
@@ -6,12 +6,10 @@ use BookStack\Actions\ActivityType;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Facades\Activity;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class BookshelfRepo
|
||||
@@ -89,6 +87,7 @@ class BookshelfRepo
|
||||
{
|
||||
$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);
|
||||
|
||||
@@ -106,14 +105,17 @@ class BookshelfRepo
|
||||
$this->updateBooks($shelf, $bookIds);
|
||||
}
|
||||
|
||||
if (array_key_exists('image', $input)) {
|
||||
$this->baseRepo->updateCoverImage($shelf, $input['image'], $input['image'] === null);
|
||||
}
|
||||
|
||||
Activity::add(ActivityType::BOOKSHELF_UPDATE, $shelf);
|
||||
|
||||
return $shelf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update which books are assigned to this shelf by
|
||||
* syncing the given book ids.
|
||||
* Update which books are assigned to this shelf by syncing the given book ids.
|
||||
* Function ensures the books are visible to the current user and existing.
|
||||
*/
|
||||
protected function updateBooks(Bookshelf $shelf, array $bookIds)
|
||||
@@ -132,17 +134,6 @@ class BookshelfRepo
|
||||
$shelf->books()->sync($syncData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given shelf cover image, or clear it.
|
||||
*
|
||||
* @throws ImageUploadException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function updateCoverImage(Bookshelf $shelf, ?UploadedFile $coverImage, bool $removeImage = false)
|
||||
{
|
||||
$this->baseRepo->updateCoverImage($shelf, $coverImage, $removeImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy down the permissions of the given shelf to all child books.
|
||||
*/
|
||||
|
||||
@@ -392,23 +392,6 @@ class PageRepo
|
||||
return $parentClass::visible()->where('id', '=', $entityId)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the page's parent to the given entity.
|
||||
*/
|
||||
protected function changeParent(Page $page, Entity $parent)
|
||||
{
|
||||
$book = ($parent instanceof Chapter) ? $parent->book : $parent;
|
||||
$page->chapter_id = ($parent instanceof Chapter) ? $parent->id : 0;
|
||||
$page->save();
|
||||
|
||||
if ($page->book->id !== $book->id) {
|
||||
$page->changeBook($book->id);
|
||||
}
|
||||
|
||||
$page->load('book');
|
||||
$book->rebuildPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a page revision to update for the given page.
|
||||
* Checks for an existing revisions before providing a fresh one.
|
||||
|
||||
@@ -16,25 +16,10 @@ use Illuminate\Http\UploadedFile;
|
||||
|
||||
class Cloner
|
||||
{
|
||||
/**
|
||||
* @var PageRepo
|
||||
*/
|
||||
protected $pageRepo;
|
||||
|
||||
/**
|
||||
* @var ChapterRepo
|
||||
*/
|
||||
protected $chapterRepo;
|
||||
|
||||
/**
|
||||
* @var BookRepo
|
||||
*/
|
||||
protected $bookRepo;
|
||||
|
||||
/**
|
||||
* @var ImageService
|
||||
*/
|
||||
protected $imageService;
|
||||
protected PageRepo $pageRepo;
|
||||
protected ChapterRepo $chapterRepo;
|
||||
protected BookRepo $bookRepo;
|
||||
protected ImageService $imageService;
|
||||
|
||||
public function __construct(PageRepo $pageRepo, ChapterRepo $chapterRepo, BookRepo $bookRepo, ImageService $imageService)
|
||||
{
|
||||
@@ -50,11 +35,8 @@ class Cloner
|
||||
public function clonePage(Page $original, Entity $parent, string $newName): Page
|
||||
{
|
||||
$copyPage = $this->pageRepo->getNewDraftPage($parent);
|
||||
$pageData = $original->getAttributes();
|
||||
|
||||
// Update name & tags
|
||||
$pageData = $this->entityToInputData($original);
|
||||
$pageData['name'] = $newName;
|
||||
$pageData['tags'] = $this->entityTagsToInputArray($original);
|
||||
|
||||
return $this->pageRepo->publishDraft($copyPage, $pageData);
|
||||
}
|
||||
@@ -65,9 +47,8 @@ class Cloner
|
||||
*/
|
||||
public function cloneChapter(Chapter $original, Book $parent, string $newName): Chapter
|
||||
{
|
||||
$chapterDetails = $original->getAttributes();
|
||||
$chapterDetails = $this->entityToInputData($original);
|
||||
$chapterDetails['name'] = $newName;
|
||||
$chapterDetails['tags'] = $this->entityTagsToInputArray($original);
|
||||
|
||||
$copyChapter = $this->chapterRepo->create($chapterDetails, $parent);
|
||||
|
||||
@@ -87,9 +68,8 @@ class Cloner
|
||||
*/
|
||||
public function cloneBook(Book $original, string $newName): Book
|
||||
{
|
||||
$bookDetails = $original->getAttributes();
|
||||
$bookDetails = $this->entityToInputData($original);
|
||||
$bookDetails['name'] = $newName;
|
||||
$bookDetails['tags'] = $this->entityTagsToInputArray($original);
|
||||
|
||||
$copyBook = $this->bookRepo->create($bookDetails);
|
||||
|
||||
@@ -104,26 +84,48 @@ class Cloner
|
||||
}
|
||||
}
|
||||
|
||||
if ($original->cover) {
|
||||
try {
|
||||
$tmpImgFile = tmpfile();
|
||||
$uploadedFile = $this->imageToUploadedFile($original->cover, $tmpImgFile);
|
||||
$this->bookRepo->updateCoverImage($copyBook, $uploadedFile, false);
|
||||
} catch (\Exception $exception) {
|
||||
}
|
||||
return $copyBook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an entity to a raw data array of input data.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function entityToInputData(Entity $entity): array
|
||||
{
|
||||
$inputData = $entity->getAttributes();
|
||||
$inputData['tags'] = $this->entityTagsToInputArray($entity);
|
||||
|
||||
// Add a cover to the data if existing on the original entity
|
||||
if ($entity->cover instanceof Image) {
|
||||
$uploadedFile = $this->imageToUploadedFile($entity->cover);
|
||||
$inputData['image'] = $uploadedFile;
|
||||
}
|
||||
|
||||
return $copyBook;
|
||||
return $inputData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the permission settings from the source entity to the target entity.
|
||||
*/
|
||||
public function copyEntityPermissions(Entity $sourceEntity, Entity $targetEntity): void
|
||||
{
|
||||
$targetEntity->restricted = $sourceEntity->restricted;
|
||||
$permissions = $sourceEntity->permissions()->get(['role_id', 'action'])->toArray();
|
||||
$targetEntity->permissions()->delete();
|
||||
$targetEntity->permissions()->createMany($permissions);
|
||||
$targetEntity->rebuildPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an image instance to an UploadedFile instance to mimic
|
||||
* a file being uploaded.
|
||||
*/
|
||||
protected function imageToUploadedFile(Image $image, &$tmpFile): ?UploadedFile
|
||||
protected function imageToUploadedFile(Image $image): ?UploadedFile
|
||||
{
|
||||
$imgData = $this->imageService->getImageData($image);
|
||||
$tmpImgFilePath = stream_get_meta_data($tmpFile)['uri'];
|
||||
$tmpImgFilePath = tempnam(sys_get_temp_dir(), 'bs_cover_clone_');
|
||||
file_put_contents($tmpImgFilePath, $imgData);
|
||||
|
||||
return new UploadedFile($tmpImgFilePath, basename($image->path));
|
||||
|
||||
@@ -39,7 +39,7 @@ class ExportFormatter
|
||||
public function pageToContainedHtml(Page $page)
|
||||
{
|
||||
$page->html = (new PageContent($page))->render();
|
||||
$pageHtml = view('pages.export', [
|
||||
$pageHtml = view('exports.page', [
|
||||
'page' => $page,
|
||||
'format' => 'html',
|
||||
'cspContent' => $this->cspService->getCspMetaTagValue(),
|
||||
@@ -59,7 +59,7 @@ class ExportFormatter
|
||||
$pages->each(function ($page) {
|
||||
$page->html = (new PageContent($page))->render();
|
||||
});
|
||||
$html = view('chapters.export', [
|
||||
$html = view('exports.chapter', [
|
||||
'chapter' => $chapter,
|
||||
'pages' => $pages,
|
||||
'format' => 'html',
|
||||
@@ -77,7 +77,7 @@ class ExportFormatter
|
||||
public function bookToContainedHtml(Book $book)
|
||||
{
|
||||
$bookTree = (new BookContents($book))->getTree(false, true);
|
||||
$html = view('books.export', [
|
||||
$html = view('exports.book', [
|
||||
'book' => $book,
|
||||
'bookChildren' => $bookTree,
|
||||
'format' => 'html',
|
||||
@@ -95,7 +95,7 @@ class ExportFormatter
|
||||
public function pageToPdf(Page $page)
|
||||
{
|
||||
$page->html = (new PageContent($page))->render();
|
||||
$html = view('pages.export', [
|
||||
$html = view('exports.page', [
|
||||
'page' => $page,
|
||||
'format' => 'pdf',
|
||||
'engine' => $this->pdfGenerator->getActiveEngine(),
|
||||
@@ -116,7 +116,7 @@ class ExportFormatter
|
||||
$page->html = (new PageContent($page))->render();
|
||||
});
|
||||
|
||||
$html = view('chapters.export', [
|
||||
$html = view('exports.chapter', [
|
||||
'chapter' => $chapter,
|
||||
'pages' => $pages,
|
||||
'format' => 'pdf',
|
||||
@@ -134,7 +134,7 @@ class ExportFormatter
|
||||
public function bookToPdf(Book $book)
|
||||
{
|
||||
$bookTree = (new BookContents($book))->getTree(false, true);
|
||||
$html = view('books.export', [
|
||||
$html = view('exports.book', [
|
||||
'book' => $book,
|
||||
'bookChildren' => $bookTree,
|
||||
'format' => 'pdf',
|
||||
|
||||
87
app/Entities/Tools/HierarchyTransformer.php
Normal file
87
app/Entities/Tools/HierarchyTransformer.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Tools;
|
||||
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
use BookStack\Entities\Repos\BookshelfRepo;
|
||||
use BookStack\Facades\Activity;
|
||||
|
||||
class HierarchyTransformer
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a chapter into a book.
|
||||
* Does not check permissions, check before calling.
|
||||
*/
|
||||
public function transformChapterToBook(Chapter $chapter): Book
|
||||
{
|
||||
$inputData = $this->cloner->entityToInputData($chapter);
|
||||
$book = $this->bookRepo->create($inputData);
|
||||
$this->cloner->copyEntityPermissions($chapter, $book);
|
||||
|
||||
/** @var Page $page */
|
||||
foreach ($chapter->pages as $page) {
|
||||
$page->chapter_id = 0;
|
||||
$page->changeBook($book->id);
|
||||
}
|
||||
|
||||
$this->trashCan->destroyEntity($chapter);
|
||||
|
||||
Activity::add(ActivityType::BOOK_CREATE_FROM_CHAPTER, $book);
|
||||
|
||||
return $book;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a book into a shelf.
|
||||
* Does not check permissions, check before calling.
|
||||
*/
|
||||
public function transformBookToShelf(Book $book): Bookshelf
|
||||
{
|
||||
$inputData = $this->cloner->entityToInputData($book);
|
||||
$shelf = $this->shelfRepo->create($inputData, []);
|
||||
$this->cloner->copyEntityPermissions($book, $shelf);
|
||||
|
||||
$shelfBookSyncData = [];
|
||||
|
||||
/** @var Chapter $chapter */
|
||||
foreach ($book->chapters as $index => $chapter) {
|
||||
$newBook = $this->transformChapterToBook($chapter);
|
||||
$shelfBookSyncData[$newBook->id] = ['order' => $index];
|
||||
if (!$newBook->restricted) {
|
||||
$this->cloner->copyEntityPermissions($shelf, $newBook);
|
||||
}
|
||||
}
|
||||
|
||||
if ($book->directPages->count() > 0) {
|
||||
$book->name .= ' ' . trans('entities.pages');
|
||||
$shelfBookSyncData[$book->id] = ['order' => count($shelfBookSyncData) + 1];
|
||||
$book->save();
|
||||
} else {
|
||||
$this->trashCan->destroyEntity($book);
|
||||
}
|
||||
|
||||
$shelf->books()->sync($shelfBookSyncData);
|
||||
|
||||
Activity::add(ActivityType::BOOKSHELF_CREATE_FROM_BOOK, $shelf);
|
||||
|
||||
return $shelf;
|
||||
}
|
||||
}
|
||||
@@ -147,6 +147,8 @@ class SearchIndex
|
||||
];
|
||||
|
||||
$html = '<body>' . $html . '</body>';
|
||||
$html = str_ireplace(['<br>', '<br />', '<br/>'], "\n", $html);
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
|
||||
|
||||
@@ -360,7 +360,7 @@ class SearchRunner
|
||||
/** @var Connection $connection */
|
||||
$connection = $query->getConnection();
|
||||
$tagValue = (float) trim($connection->getPdo()->quote($tagValue), "'");
|
||||
$query->whereRaw("value ${tagOperator} ${tagValue}");
|
||||
$query->whereRaw("value {$tagOperator} {$tagValue}");
|
||||
} else {
|
||||
$query->where('value', $tagOperator, $tagValue);
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ class TrashCan
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function destroyEntity(Entity $entity): int
|
||||
public function destroyEntity(Entity $entity): int
|
||||
{
|
||||
if ($entity instanceof Page) {
|
||||
return $this->destroyPage($entity);
|
||||
|
||||
@@ -21,6 +21,7 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
protected $dontReport = [
|
||||
NotFoundException::class,
|
||||
StoppedAuthenticationException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,10 +19,13 @@ class JsonDebugException extends Exception
|
||||
}
|
||||
|
||||
/**
|
||||
* Covert this exception into a response.
|
||||
* Convert this exception into a response.
|
||||
* We add a manual data conversion to UTF8 to ensure any binary data is presentable as a JSON string.
|
||||
*/
|
||||
public function render(): JsonResponse
|
||||
{
|
||||
return response()->json($this->data);
|
||||
$cleaned = mb_convert_encoding($this->data, 'UTF-8');
|
||||
|
||||
return response()->json($cleaned);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,19 +11,6 @@ class BookApiController extends ApiController
|
||||
{
|
||||
protected $bookRepo;
|
||||
|
||||
protected $rules = [
|
||||
'create' => [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'tags' => ['array'],
|
||||
],
|
||||
'update' => [
|
||||
'name' => ['string', 'min:1', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'tags' => ['array'],
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct(BookRepo $bookRepo)
|
||||
{
|
||||
$this->bookRepo = $bookRepo;
|
||||
@@ -37,19 +24,21 @@ class BookApiController extends ApiController
|
||||
$books = Book::visible();
|
||||
|
||||
return $this->apiListingResponse($books, [
|
||||
'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by', 'image_id',
|
||||
'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new book in the system.
|
||||
* The cover image of a book can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
|
||||
* If the 'image' property is null then the book cover image will be removed.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->checkPermission('book-create-all');
|
||||
$requestData = $this->validate($request, $this->rules['create']);
|
||||
$requestData = $this->validate($request, $this->rules()['create']);
|
||||
|
||||
$book = $this->bookRepo->create($requestData);
|
||||
|
||||
@@ -68,6 +57,8 @@ class BookApiController extends ApiController
|
||||
|
||||
/**
|
||||
* Update the details of a single book.
|
||||
* The cover image of a book can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
|
||||
* If the 'image' property is null then the book cover image will be removed.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
@@ -76,7 +67,7 @@ class BookApiController extends ApiController
|
||||
$book = Book::visible()->findOrFail($id);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
|
||||
$requestData = $this->validate($request, $this->rules['update']);
|
||||
$requestData = $this->validate($request, $this->rules()['update']);
|
||||
$book = $this->bookRepo->update($book, $requestData);
|
||||
|
||||
return response()->json($book);
|
||||
@@ -97,4 +88,22 @@ class BookApiController extends ApiController
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'create' => [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'tags' => ['array'],
|
||||
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
|
||||
],
|
||||
'update' => [
|
||||
'name' => ['string', 'min:1', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'tags' => ['array'],
|
||||
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class BookExportApiController extends ApiController
|
||||
$book = Book::visible()->findOrFail($id);
|
||||
$pdfContent = $this->exportFormatter->bookToPdf($book);
|
||||
|
||||
return $this->downloadResponse($pdfContent, $book->slug . '.pdf');
|
||||
return $this->download()->directly($pdfContent, $book->slug . '.pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ class BookExportApiController extends ApiController
|
||||
$book = Book::visible()->findOrFail($id);
|
||||
$htmlContent = $this->exportFormatter->bookToContainedHtml($book);
|
||||
|
||||
return $this->downloadResponse($htmlContent, $book->slug . '.html');
|
||||
return $this->download()->directly($htmlContent, $book->slug . '.html');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ class BookExportApiController extends ApiController
|
||||
$book = Book::visible()->findOrFail($id);
|
||||
$textContent = $this->exportFormatter->bookToPlainText($book);
|
||||
|
||||
return $this->downloadResponse($textContent, $book->slug . '.txt');
|
||||
return $this->download()->directly($textContent, $book->slug . '.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,6 +61,6 @@ class BookExportApiController extends ApiController
|
||||
$book = Book::visible()->findOrFail($id);
|
||||
$markdown = $this->exportFormatter->bookToMarkdown($book);
|
||||
|
||||
return $this->downloadResponse($markdown, $book->slug . '.md');
|
||||
return $this->download()->directly($markdown, $book->slug . '.md');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,6 @@ class BookshelfApiController extends ApiController
|
||||
{
|
||||
protected BookshelfRepo $bookshelfRepo;
|
||||
|
||||
protected $rules = [
|
||||
'create' => [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'books' => ['array'],
|
||||
'tags' => ['array'],
|
||||
],
|
||||
'update' => [
|
||||
'name' => ['string', 'min:1', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'books' => ['array'],
|
||||
'tags' => ['array'],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* BookshelfApiController constructor.
|
||||
*/
|
||||
@@ -44,7 +29,7 @@ class BookshelfApiController extends ApiController
|
||||
$shelves = Bookshelf::visible();
|
||||
|
||||
return $this->apiListingResponse($shelves, [
|
||||
'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by', 'image_id',
|
||||
'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -52,13 +37,15 @@ class BookshelfApiController extends ApiController
|
||||
* Create a new shelf in the system.
|
||||
* An array of books IDs can be provided in the request. These
|
||||
* will be added to the shelf in the same order as provided.
|
||||
* The cover image of a shelf can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
|
||||
* If the 'image' property is null then the shelf cover image will be removed.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->checkPermission('bookshelf-create-all');
|
||||
$requestData = $this->validate($request, $this->rules['create']);
|
||||
$requestData = $this->validate($request, $this->rules()['create']);
|
||||
|
||||
$bookIds = $request->get('books', []);
|
||||
$shelf = $this->bookshelfRepo->create($requestData, $bookIds);
|
||||
@@ -86,6 +73,8 @@ class BookshelfApiController extends ApiController
|
||||
* An array of books IDs can be provided in the request. These
|
||||
* will be added to the shelf in the same order as provided and overwrite
|
||||
* any existing book assignments.
|
||||
* The cover image of a shelf can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
|
||||
* If the 'image' property is null then the shelf cover image will be removed.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
@@ -94,7 +83,7 @@ class BookshelfApiController extends ApiController
|
||||
$shelf = Bookshelf::visible()->findOrFail($id);
|
||||
$this->checkOwnablePermission('bookshelf-update', $shelf);
|
||||
|
||||
$requestData = $this->validate($request, $this->rules['update']);
|
||||
$requestData = $this->validate($request, $this->rules()['update']);
|
||||
$bookIds = $request->get('books', null);
|
||||
|
||||
$shelf = $this->bookshelfRepo->update($shelf, $requestData, $bookIds);
|
||||
@@ -117,4 +106,24 @@ class BookshelfApiController extends ApiController
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'create' => [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'books' => ['array'],
|
||||
'tags' => ['array'],
|
||||
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
|
||||
],
|
||||
'update' => [
|
||||
'name' => ['string', 'min:1', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'books' => ['array'],
|
||||
'tags' => ['array'],
|
||||
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class ChapterExportApiController extends ApiController
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$pdfContent = $this->exportFormatter->chapterToPdf($chapter);
|
||||
|
||||
return $this->downloadResponse($pdfContent, $chapter->slug . '.pdf');
|
||||
return $this->download()->directly($pdfContent, $chapter->slug . '.pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,7 +42,7 @@ class ChapterExportApiController extends ApiController
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$htmlContent = $this->exportFormatter->chapterToContainedHtml($chapter);
|
||||
|
||||
return $this->downloadResponse($htmlContent, $chapter->slug . '.html');
|
||||
return $this->download()->directly($htmlContent, $chapter->slug . '.html');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +53,7 @@ class ChapterExportApiController extends ApiController
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$textContent = $this->exportFormatter->chapterToPlainText($chapter);
|
||||
|
||||
return $this->downloadResponse($textContent, $chapter->slug . '.txt');
|
||||
return $this->download()->directly($textContent, $chapter->slug . '.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +64,6 @@ class ChapterExportApiController extends ApiController
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$markdown = $this->exportFormatter->chapterToMarkdown($chapter);
|
||||
|
||||
return $this->downloadResponse($markdown, $chapter->slug . '.md');
|
||||
return $this->download()->directly($markdown, $chapter->slug . '.md');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class PageExportApiController extends ApiController
|
||||
$page = Page::visible()->findOrFail($id);
|
||||
$pdfContent = $this->exportFormatter->pageToPdf($page);
|
||||
|
||||
return $this->downloadResponse($pdfContent, $page->slug . '.pdf');
|
||||
return $this->download()->directly($pdfContent, $page->slug . '.pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ class PageExportApiController extends ApiController
|
||||
$page = Page::visible()->findOrFail($id);
|
||||
$htmlContent = $this->exportFormatter->pageToContainedHtml($page);
|
||||
|
||||
return $this->downloadResponse($htmlContent, $page->slug . '.html');
|
||||
return $this->download()->directly($htmlContent, $page->slug . '.html');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ class PageExportApiController extends ApiController
|
||||
$page = Page::visible()->findOrFail($id);
|
||||
$textContent = $this->exportFormatter->pageToPlainText($page);
|
||||
|
||||
return $this->downloadResponse($textContent, $page->slug . '.txt');
|
||||
return $this->download()->directly($textContent, $page->slug . '.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,6 +61,6 @@ class PageExportApiController extends ApiController
|
||||
$page = Page::visible()->findOrFail($id);
|
||||
$markdown = $this->exportFormatter->pageToMarkdown($page);
|
||||
|
||||
return $this->downloadResponse($markdown, $page->slug . '.md');
|
||||
return $this->download()->directly($markdown, $page->slug . '.md');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,10 +233,10 @@ class AttachmentController extends Controller
|
||||
$attachmentStream = $this->attachmentService->streamAttachmentFromStorage($attachment);
|
||||
|
||||
if ($request->get('open') === 'true') {
|
||||
return $this->streamedInlineDownloadResponse($attachmentStream, $fileName);
|
||||
return $this->download()->streamedInline($attachmentStream, $fileName);
|
||||
}
|
||||
|
||||
return $this->streamedDownloadResponse($attachmentStream, $fileName);
|
||||
return $this->download()->streamedDirectly($attachmentStream, $fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,17 +25,16 @@ class LoginController extends Controller
|
||||
|
|
||||
*/
|
||||
|
||||
use AuthenticatesUsers;
|
||||
use AuthenticatesUsers { logout as traitLogout; }
|
||||
|
||||
/**
|
||||
* Redirection paths.
|
||||
*/
|
||||
protected $redirectTo = '/';
|
||||
protected $redirectPath = '/';
|
||||
protected $redirectAfterLogout = '/login';
|
||||
|
||||
protected $socialAuthService;
|
||||
protected $loginService;
|
||||
protected SocialAuthService $socialAuthService;
|
||||
protected LoginService $loginService;
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
@@ -50,7 +49,6 @@ class LoginController extends Controller
|
||||
$this->loginService = $loginService;
|
||||
|
||||
$this->redirectPath = url('/');
|
||||
$this->redirectAfterLogout = url('/login');
|
||||
}
|
||||
|
||||
public function username()
|
||||
@@ -73,6 +71,7 @@ class LoginController extends Controller
|
||||
{
|
||||
$socialDrivers = $this->socialAuthService->getActiveDrivers();
|
||||
$authMethod = config('auth.method');
|
||||
$preventInitiation = $request->get('prevent_auto_init') === 'true';
|
||||
|
||||
if ($request->has('email')) {
|
||||
session()->flashInput([
|
||||
@@ -84,6 +83,12 @@ class LoginController extends Controller
|
||||
// Store the previous location for redirect after login
|
||||
$this->updateIntendedFromPrevious();
|
||||
|
||||
if (!$preventInitiation && $this->shouldAutoInitiate()) {
|
||||
return view('auth.login-initiate', [
|
||||
'authMethod' => $authMethod,
|
||||
]);
|
||||
}
|
||||
|
||||
return view('auth.login', [
|
||||
'socialDrivers' => $socialDrivers,
|
||||
'authMethod' => $authMethod,
|
||||
@@ -251,4 +256,32 @@ class LoginController extends Controller
|
||||
|
||||
redirect()->setIntendedUrl($previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if login auto-initiate should be valid based upon authentication config.
|
||||
*/
|
||||
protected function shouldAutoInitiate(): bool
|
||||
{
|
||||
$socialDrivers = $this->socialAuthService->getActiveDrivers();
|
||||
$authMethod = config('auth.method');
|
||||
$autoRedirect = config('auth.auto_initiate');
|
||||
|
||||
return $autoRedirect && count($socialDrivers) === 0 && in_array($authMethod, ['oidc', 'saml2']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout user and perform subsequent redirect.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$this->traitLogout($request);
|
||||
|
||||
$redirectUri = $this->shouldAutoInitiate() ? '/login?prevent_auto_init=true' : '/';
|
||||
|
||||
return redirect($redirectUri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
use BookStack\Entities\Tools\Cloner;
|
||||
use BookStack\Entities\Tools\HierarchyTransformer;
|
||||
use BookStack\Entities\Tools\PermissionsUpdater;
|
||||
use BookStack\Entities\Tools\ShelfContext;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
@@ -87,10 +88,11 @@ class BookController extends Controller
|
||||
public function store(Request $request, string $shelfSlug = null)
|
||||
{
|
||||
$this->checkPermission('book-create-all');
|
||||
$this->validate($request, [
|
||||
$validated = $this->validate($request, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
|
||||
'tags' => ['array'],
|
||||
]);
|
||||
|
||||
$bookshelf = null;
|
||||
@@ -99,8 +101,7 @@ class BookController extends Controller
|
||||
$this->checkOwnablePermission('bookshelf-update', $bookshelf);
|
||||
}
|
||||
|
||||
$book = $this->bookRepo->create($request->all());
|
||||
$this->bookRepo->updateCoverImage($book, $request->file('image', null));
|
||||
$book = $this->bookRepo->create($validated);
|
||||
|
||||
if ($bookshelf) {
|
||||
$bookshelf->appendBook($book);
|
||||
@@ -158,15 +159,21 @@ class BookController extends Controller
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($slug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
$this->validate($request, [
|
||||
|
||||
$validated = $this->validate($request, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
|
||||
'tags' => ['array'],
|
||||
]);
|
||||
|
||||
$book = $this->bookRepo->update($book, $request->all());
|
||||
$resetCover = $request->has('image_reset');
|
||||
$this->bookRepo->updateCoverImage($book, $request->file('image', null), $resetCover);
|
||||
if ($request->has('image_reset')) {
|
||||
$validated['image'] = null;
|
||||
} elseif (array_key_exists('image', $validated) && is_null($validated['image'])) {
|
||||
unset($validated['image']);
|
||||
}
|
||||
|
||||
$book = $this->bookRepo->update($book, $validated);
|
||||
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
@@ -262,4 +269,20 @@ class BookController extends Controller
|
||||
|
||||
return redirect($bookCopy->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the chapter to a book.
|
||||
*/
|
||||
public function convertToShelf(HierarchyTransformer $transformer, string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
$this->checkOwnablePermission('book-delete', $book);
|
||||
$this->checkPermission('bookshelf-create-all');
|
||||
$this->checkPermission('book-create-all');
|
||||
|
||||
$shelf = $transformer->transformBookToShelf($book);
|
||||
|
||||
return redirect($shelf->getUrl());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class BookExportController extends Controller
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$pdfContent = $this->exportFormatter->bookToPdf($book);
|
||||
|
||||
return $this->downloadResponse($pdfContent, $bookSlug . '.pdf');
|
||||
return $this->download()->directly($pdfContent, $bookSlug . '.pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ class BookExportController extends Controller
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$htmlContent = $this->exportFormatter->bookToContainedHtml($book);
|
||||
|
||||
return $this->downloadResponse($htmlContent, $bookSlug . '.html');
|
||||
return $this->download()->directly($htmlContent, $bookSlug . '.html');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,7 +55,7 @@ class BookExportController extends Controller
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$textContent = $this->exportFormatter->bookToPlainText($book);
|
||||
|
||||
return $this->downloadResponse($textContent, $bookSlug . '.txt');
|
||||
return $this->download()->directly($textContent, $bookSlug . '.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +66,6 @@ class BookExportController extends Controller
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$textContent = $this->exportFormatter->bookToMarkdown($book);
|
||||
|
||||
return $this->downloadResponse($textContent, $bookSlug . '.md');
|
||||
return $this->download()->directly($textContent, $bookSlug . '.md');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,15 +83,15 @@ class BookshelfController extends Controller
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->checkPermission('bookshelf-create-all');
|
||||
$this->validate($request, [
|
||||
$validated = $this->validate($request, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
|
||||
'tags' => ['array'],
|
||||
]);
|
||||
|
||||
$bookIds = explode(',', $request->get('books', ''));
|
||||
$shelf = $this->bookshelfRepo->create($request->all(), $bookIds);
|
||||
$this->bookshelfRepo->updateCoverImage($shelf, $request->file('image', null));
|
||||
$shelf = $this->bookshelfRepo->create($validated, $bookIds);
|
||||
|
||||
return redirect($shelf->getUrl());
|
||||
}
|
||||
@@ -160,16 +160,21 @@ class BookshelfController extends Controller
|
||||
{
|
||||
$shelf = $this->bookshelfRepo->getBySlug($slug);
|
||||
$this->checkOwnablePermission('bookshelf-update', $shelf);
|
||||
$this->validate($request, [
|
||||
$validated = $this->validate($request, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['string', 'max:1000'],
|
||||
'image' => array_merge(['nullable'], $this->getImageValidationRules()),
|
||||
'tags' => ['array'],
|
||||
]);
|
||||
|
||||
if ($request->has('image_reset')) {
|
||||
$validated['image'] = null;
|
||||
} elseif (array_key_exists('image', $validated) && is_null($validated['image'])) {
|
||||
unset($validated['image']);
|
||||
}
|
||||
|
||||
$bookIds = explode(',', $request->get('books', ''));
|
||||
$shelf = $this->bookshelfRepo->update($shelf, $request->all(), $bookIds);
|
||||
$resetCover = $request->has('image_reset');
|
||||
$this->bookshelfRepo->updateCoverImage($shelf, $request->file('image', null), $resetCover);
|
||||
$shelf = $this->bookshelfRepo->update($shelf, $validated, $bookIds);
|
||||
|
||||
return redirect($shelf->getUrl());
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Repos\ChapterRepo;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
use BookStack\Entities\Tools\Cloner;
|
||||
use BookStack\Entities\Tools\HierarchyTransformer;
|
||||
use BookStack\Entities\Tools\NextPreviousContentLocator;
|
||||
use BookStack\Entities\Tools\PermissionsUpdater;
|
||||
use BookStack\Exceptions\MoveOperationException;
|
||||
@@ -272,4 +273,19 @@ class ChapterController extends Controller
|
||||
|
||||
return redirect($chapter->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the chapter to a book.
|
||||
*/
|
||||
public function convertToBook(HierarchyTransformer $transformer, string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
$this->checkPermission('book-create-all');
|
||||
|
||||
$book = $transformer->transformChapterToBook($chapter);
|
||||
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class ChapterExportController extends Controller
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$pdfContent = $this->exportFormatter->chapterToPdf($chapter);
|
||||
|
||||
return $this->downloadResponse($pdfContent, $chapterSlug . '.pdf');
|
||||
return $this->download()->directly($pdfContent, $chapterSlug . '.pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +47,7 @@ class ChapterExportController extends Controller
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$containedHtml = $this->exportFormatter->chapterToContainedHtml($chapter);
|
||||
|
||||
return $this->downloadResponse($containedHtml, $chapterSlug . '.html');
|
||||
return $this->download()->directly($containedHtml, $chapterSlug . '.html');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +60,7 @@ class ChapterExportController extends Controller
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapterText = $this->exportFormatter->chapterToPlainText($chapter);
|
||||
|
||||
return $this->downloadResponse($chapterText, $chapterSlug . '.txt');
|
||||
return $this->download()->directly($chapterText, $chapterSlug . '.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,10 +70,9 @@ class ChapterExportController extends Controller
|
||||
*/
|
||||
public function markdown(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
// TODO: This should probably export to a zip file.
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapterText = $this->exportFormatter->chapterToMarkdown($chapter);
|
||||
|
||||
return $this->downloadResponse($chapterText, $chapterSlug . '.md');
|
||||
return $this->download()->directly($chapterText, $chapterSlug . '.md');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,13 @@ namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Exceptions\NotifyException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Http\Responses\DownloadResponseFactory;
|
||||
use BookStack\Interfaces\Loggable;
|
||||
use BookStack\Model;
|
||||
use BookStack\Util\WebSafeMimeSniffer;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
@@ -110,72 +108,11 @@ abstract class Controller extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a response that forces a download in the browser.
|
||||
* Create and return a new download response factory using the current request.
|
||||
*/
|
||||
protected function downloadResponse(string $content, string $fileName): Response
|
||||
protected function download(): DownloadResponseFactory
|
||||
{
|
||||
return response()->make($content, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a response that forces a download, from a given stream of content.
|
||||
*/
|
||||
protected function streamedDownloadResponse($stream, string $fileName): StreamedResponse
|
||||
{
|
||||
return response()->stream(function () use ($stream) {
|
||||
// End & flush the output buffer otherwise we still seem to use memory.
|
||||
// Ignore in testing since output buffers are used to gather a response.
|
||||
if (!app()->runningUnitTests()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
}, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file download response that provides the file with a content-type
|
||||
* correct for the file, in a way so the browser can show the content in browser.
|
||||
*/
|
||||
protected function inlineDownloadResponse(string $content, string $fileName): Response
|
||||
{
|
||||
$mime = (new WebSafeMimeSniffer())->sniff($content);
|
||||
|
||||
return response()->make($content, 200, [
|
||||
'Content-Type' => $mime,
|
||||
'Content-Disposition' => 'inline; filename="' . str_replace('"', '', $fileName) . '"',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file download response that provides the file with a content-type
|
||||
* correct for the file, in a way so the browser can show the content in browser,
|
||||
* for a given content stream.
|
||||
*/
|
||||
protected function streamedInlineDownloadResponse($stream, string $fileName): StreamedResponse
|
||||
{
|
||||
$sniffContent = fread($stream, 1000);
|
||||
$mime = (new WebSafeMimeSniffer())->sniff($sniffContent);
|
||||
|
||||
return response()->stream(function () use ($sniffContent, $stream) {
|
||||
echo $sniffContent;
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
}, 200, [
|
||||
'Content-Type' => $mime,
|
||||
'Content-Disposition' => 'inline; filename="' . str_replace('"', '', $fileName) . '"',
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
]);
|
||||
return new DownloadResponseFactory(request());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ class PageExportController extends Controller
|
||||
$page->html = (new PageContent($page))->render();
|
||||
$pdfContent = $this->exportFormatter->pageToPdf($page);
|
||||
|
||||
return $this->downloadResponse($pdfContent, $pageSlug . '.pdf');
|
||||
return $this->download()->directly($pdfContent, $pageSlug . '.pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,7 +51,7 @@ class PageExportController extends Controller
|
||||
$page->html = (new PageContent($page))->render();
|
||||
$containedHtml = $this->exportFormatter->pageToContainedHtml($page);
|
||||
|
||||
return $this->downloadResponse($containedHtml, $pageSlug . '.html');
|
||||
return $this->download()->directly($containedHtml, $pageSlug . '.html');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +64,7 @@ class PageExportController extends Controller
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$pageText = $this->exportFormatter->pageToPlainText($page);
|
||||
|
||||
return $this->downloadResponse($pageText, $pageSlug . '.txt');
|
||||
return $this->download()->directly($pageText, $pageSlug . '.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,6 +77,6 @@ class PageExportController extends Controller
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$pageText = $this->exportFormatter->pageToMarkdown($page);
|
||||
|
||||
return $this->downloadResponse($pageText, $pageSlug . '.md');
|
||||
return $this->download()->directly($pageText, $pageSlug . '.md');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class SettingController extends Controller
|
||||
$this->logActivity(ActivityType::SETTINGS_UPDATE, $category);
|
||||
$this->showSuccessNotification(trans('settings.settings_save_success'));
|
||||
|
||||
return redirect("/settings/${category}");
|
||||
return redirect("/settings/{$category}");
|
||||
}
|
||||
|
||||
protected function ensureCategoryExists(string $category): void
|
||||
|
||||
@@ -11,7 +11,7 @@ class Localization
|
||||
/**
|
||||
* Array of right-to-left locales.
|
||||
*/
|
||||
protected $rtlLocales = ['ar', 'he'];
|
||||
protected $rtlLocales = ['ar', 'fa', 'he'];
|
||||
|
||||
/**
|
||||
* Map of BookStack locale names to best-estimate system locale names.
|
||||
@@ -30,6 +30,7 @@ class Localization
|
||||
'es_AR' => 'es_AR',
|
||||
'et' => 'et_EE',
|
||||
'eu' => 'eu_ES',
|
||||
'fa' => 'fa_IR',
|
||||
'fr' => 'fr_FR',
|
||||
'he' => 'he_IL',
|
||||
'hr' => 'hr_HR',
|
||||
|
||||
@@ -35,7 +35,9 @@ class Request extends LaravelRequest
|
||||
$appUrl = config('app.url', null);
|
||||
|
||||
if ($appUrl) {
|
||||
return '/' . rtrim(implode('/', array_slice(explode('/', $appUrl), 3)), '/');
|
||||
$parsedBaseUrl = rtrim(implode('/', array_slice(explode('/', $appUrl), 3)), '/');
|
||||
|
||||
return empty($parsedBaseUrl) ? '' : ('/' . $parsedBaseUrl);
|
||||
}
|
||||
|
||||
return parent::getBaseUrl();
|
||||
|
||||
77
app/Http/Responses/DownloadResponseFactory.php
Normal file
77
app/Http/Responses/DownloadResponseFactory.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Http\Responses;
|
||||
|
||||
use BookStack\Util\WebSafeMimeSniffer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class DownloadResponseFactory
|
||||
{
|
||||
protected Request $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a response that directly forces a download in the browser.
|
||||
*/
|
||||
public function directly(string $content, string $fileName): Response
|
||||
{
|
||||
return response()->make($content, 200, $this->getHeaders($fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a response that forces a download, from a given stream of content.
|
||||
*/
|
||||
public function streamedDirectly($stream, string $fileName): StreamedResponse
|
||||
{
|
||||
return response()->stream(function () use ($stream) {
|
||||
|
||||
// End & flush the output buffer, if we're in one, otherwise we still use memory.
|
||||
// Output buffer may or may not exist depending on PHP `output_buffering` setting.
|
||||
// Ignore in testing since output buffers are used to gather a response.
|
||||
if (!empty(ob_get_status()) && !app()->runningUnitTests()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
}, 200, $this->getHeaders($fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file download response that provides the file with a content-type
|
||||
* correct for the file, in a way so the browser can show the content in browser,
|
||||
* for a given content stream.
|
||||
*/
|
||||
public function streamedInline($stream, string $fileName): StreamedResponse
|
||||
{
|
||||
$sniffContent = fread($stream, 2000);
|
||||
$mime = (new WebSafeMimeSniffer())->sniff($sniffContent);
|
||||
|
||||
return response()->stream(function () use ($sniffContent, $stream) {
|
||||
echo $sniffContent;
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
}, 200, $this->getHeaders($fileName, $mime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the common headers to provide for a download response.
|
||||
*/
|
||||
protected function getHeaders(string $fileName, string $mime = 'application/octet-stream'): array
|
||||
{
|
||||
$disposition = ($mime === 'application/octet-stream') ? 'attachment' : 'inline';
|
||||
$downloadName = str_replace('"', '', $fileName);
|
||||
|
||||
return [
|
||||
'Content-Type' => $mime,
|
||||
'Content-Disposition' => "{$disposition}; filename=\"{$downloadName}\"",
|
||||
'X-Content-Type-Options' => 'nosniff',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -63,16 +63,6 @@ class AttachmentService
|
||||
return 'uploads/files/' . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attachment from storage.
|
||||
*
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public function getAttachmentFromStorage(Attachment $attachment): string
|
||||
{
|
||||
return $this->getStorageDisk()->get($this->adjustPathForStorageDisk($attachment->path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream an attachment from storage.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,14 @@ class WebSafeMimeSniffer
|
||||
'application/json',
|
||||
'application/octet-stream',
|
||||
'application/pdf',
|
||||
'audio/aac',
|
||||
'audio/midi',
|
||||
'audio/mpeg',
|
||||
'audio/ogg',
|
||||
'audio/opus',
|
||||
'audio/wav',
|
||||
'audio/webm',
|
||||
'audio/x-m4a',
|
||||
'image/apng',
|
||||
'image/bmp',
|
||||
'image/jpeg',
|
||||
|
||||
634
composer.lock
generated
634
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -21,8 +21,9 @@ class RoleFactory extends Factory
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'display_name' => $this->faker->sentence(3),
|
||||
'description' => $this->faker->sentence(10),
|
||||
'display_name' => $this->faker->sentence(3),
|
||||
'description' => $this->faker->sentence(10),
|
||||
'external_auth_id' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
"updated_at": "2019-12-11T20:57:31.000000Z",
|
||||
"created_by": 1,
|
||||
"updated_by": 1,
|
||||
"owned_by": 1,
|
||||
"image_id": 3
|
||||
"owned_by": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
@@ -21,8 +20,7 @@
|
||||
"updated_at": "2019-12-11T20:57:23.000000Z",
|
||||
"created_by": 4,
|
||||
"updated_by": 3,
|
||||
"owned_by": 3,
|
||||
"image_id": 34
|
||||
"owned_by": 3
|
||||
}
|
||||
],
|
||||
"total": 14
|
||||
|
||||
@@ -7,6 +7,5 @@
|
||||
"updated_at": "2020-01-12T14:16:10.000000Z",
|
||||
"created_by": 1,
|
||||
"updated_by": 1,
|
||||
"owned_by": 1,
|
||||
"image_id": 452
|
||||
"owned_by": 1
|
||||
}
|
||||
@@ -9,8 +9,7 @@
|
||||
"updated_at": "2020-04-10T13:00:45.000000Z",
|
||||
"created_by": 4,
|
||||
"updated_by": 1,
|
||||
"owned_by": 1,
|
||||
"image_id": 31
|
||||
"owned_by": 1
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
@@ -21,8 +20,7 @@
|
||||
"updated_at": "2020-04-10T13:00:58.000000Z",
|
||||
"created_by": 4,
|
||||
"updated_by": 1,
|
||||
"owned_by": 1,
|
||||
"image_id": 28
|
||||
"owned_by": 1
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
@@ -33,8 +31,7 @@
|
||||
"updated_at": "2020-04-10T13:00:53.000000Z",
|
||||
"created_by": 4,
|
||||
"updated_by": 1,
|
||||
"owned_by": 4,
|
||||
"image_id": 30
|
||||
"owned_by": 4
|
||||
}
|
||||
],
|
||||
"total": 3
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"created_by": 1,
|
||||
"updated_by": 1,
|
||||
"owned_by": 1,
|
||||
"image_id": 501,
|
||||
"created_at": "2020-04-10T13:24:09.000000Z",
|
||||
"updated_at": "2020-04-10T13:48:22.000000Z"
|
||||
}
|
||||
@@ -13,7 +13,8 @@ You'll need to tell BookStack to use your theme via the `APP_THEME` option in yo
|
||||
|
||||
## Customizing View Files
|
||||
|
||||
Content placed in your `themes/<theme_name>/` folder will override the original view files found in the `resources/views` folder. These files are typically [Laravel Blade](https://laravel.com/docs/6.x/blade) files.
|
||||
Content placed in your `themes/<theme_name>/` folder will override the original view files found in the `resources/views` folder. These files are typically [Laravel Blade](https://laravel.com/docs/8.x/blade) files.
|
||||
As an example, I could override the `resources/views/books/parts/list-item.blade.php` file with my own template at the path `themes/<theme_name>/books/parts/list-item.blade.php`.
|
||||
|
||||
## Customizing Icons
|
||||
|
||||
|
||||
439
package-lock.json
generated
439
package-lock.json
generated
@@ -5,20 +5,21 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"clipboard": "^2.0.10",
|
||||
"codemirror": "^5.65.2",
|
||||
"clipboard": "^2.0.11",
|
||||
"codemirror": "^5.65.5",
|
||||
"dropzone": "^5.9.3",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"snabbdom": "^3.5.0",
|
||||
"sortablejs": "^1.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chokidar-cli": "^3.0",
|
||||
"esbuild": "0.14.36",
|
||||
"esbuild": "0.14.42",
|
||||
"livereload": "^0.9.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"punycode": "^2.1.1",
|
||||
"sass": "^1.50.0"
|
||||
"sass": "^1.52.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
@@ -173,9 +174,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/clipboard": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.10.tgz",
|
||||
"integrity": "sha512-cz3m2YVwFz95qSEbCDi2fzLN/epEN9zXBvfgAoGkvGOJZATMl9gtTDVOtBYkx2ODUJl2kvmud7n32sV2BpYR4g==",
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
|
||||
"integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
|
||||
"dependencies": {
|
||||
"good-listener": "^1.2.2",
|
||||
"select": "^1.1.2",
|
||||
@@ -194,9 +195,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "5.65.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.2.tgz",
|
||||
"integrity": "sha512-SZM4Zq7XEC8Fhroqe3LxbEEX1zUPWH1wMr5zxiBuiUF64iYOUH/JI88v4tBag8MiBS8B8gRv8O1pPXGYXQ4ErA=="
|
||||
"version": "5.65.5",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.5.tgz",
|
||||
"integrity": "sha512-HNyhvGLnYz5c+kIsB9QKVitiZUevha3ovbIYaQiGzKo7ECSL/elWD9RXt3JgNr0NdnyqE9/Rc/7uLfkJQL638w=="
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
@@ -273,9 +274,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
|
||||
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
|
||||
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
@@ -341,9 +345,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.36.tgz",
|
||||
"integrity": "sha512-HhFHPiRXGYOCRlrhpiVDYKcFJRdO0sBElZ668M4lh2ER0YgnkLxECuFe7uWCf23FrcLc59Pqr7dHkTqmRPDHmw==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.42.tgz",
|
||||
"integrity": "sha512-V0uPZotCEHokJdNqyozH6qsaQXqmZEOiZWrXnds/zaH/0SyrIayRXWRB98CENO73MIZ9T3HBIOsmds5twWtmgw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
@@ -353,32 +357,32 @@
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"esbuild-android-64": "0.14.36",
|
||||
"esbuild-android-arm64": "0.14.36",
|
||||
"esbuild-darwin-64": "0.14.36",
|
||||
"esbuild-darwin-arm64": "0.14.36",
|
||||
"esbuild-freebsd-64": "0.14.36",
|
||||
"esbuild-freebsd-arm64": "0.14.36",
|
||||
"esbuild-linux-32": "0.14.36",
|
||||
"esbuild-linux-64": "0.14.36",
|
||||
"esbuild-linux-arm": "0.14.36",
|
||||
"esbuild-linux-arm64": "0.14.36",
|
||||
"esbuild-linux-mips64le": "0.14.36",
|
||||
"esbuild-linux-ppc64le": "0.14.36",
|
||||
"esbuild-linux-riscv64": "0.14.36",
|
||||
"esbuild-linux-s390x": "0.14.36",
|
||||
"esbuild-netbsd-64": "0.14.36",
|
||||
"esbuild-openbsd-64": "0.14.36",
|
||||
"esbuild-sunos-64": "0.14.36",
|
||||
"esbuild-windows-32": "0.14.36",
|
||||
"esbuild-windows-64": "0.14.36",
|
||||
"esbuild-windows-arm64": "0.14.36"
|
||||
"esbuild-android-64": "0.14.42",
|
||||
"esbuild-android-arm64": "0.14.42",
|
||||
"esbuild-darwin-64": "0.14.42",
|
||||
"esbuild-darwin-arm64": "0.14.42",
|
||||
"esbuild-freebsd-64": "0.14.42",
|
||||
"esbuild-freebsd-arm64": "0.14.42",
|
||||
"esbuild-linux-32": "0.14.42",
|
||||
"esbuild-linux-64": "0.14.42",
|
||||
"esbuild-linux-arm": "0.14.42",
|
||||
"esbuild-linux-arm64": "0.14.42",
|
||||
"esbuild-linux-mips64le": "0.14.42",
|
||||
"esbuild-linux-ppc64le": "0.14.42",
|
||||
"esbuild-linux-riscv64": "0.14.42",
|
||||
"esbuild-linux-s390x": "0.14.42",
|
||||
"esbuild-netbsd-64": "0.14.42",
|
||||
"esbuild-openbsd-64": "0.14.42",
|
||||
"esbuild-sunos-64": "0.14.42",
|
||||
"esbuild-windows-32": "0.14.42",
|
||||
"esbuild-windows-64": "0.14.42",
|
||||
"esbuild-windows-arm64": "0.14.42"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.36.tgz",
|
||||
"integrity": "sha512-jwpBhF1jmo0tVCYC/ORzVN+hyVcNZUWuozGcLHfod0RJCedTDTvR4nwlTXdx1gtncDqjk33itjO+27OZHbiavw==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.42.tgz",
|
||||
"integrity": "sha512-P4Y36VUtRhK/zivqGVMqhptSrFILAGlYp0Z8r9UQqHJ3iWztRCNWnlBzD9HRx0DbueXikzOiwyOri+ojAFfW6A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -392,9 +396,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-arm64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-/hYkyFe7x7Yapmfv4X/tBmyKnggUmdQmlvZ8ZlBnV4+PjisrEhAvC3yWpURuD9XoB8Wa1d5dGkTsF53pIvpjsg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.42.tgz",
|
||||
"integrity": "sha512-0cOqCubq+RWScPqvtQdjXG3Czb3AWI2CaKw3HeXry2eoA2rrPr85HF7IpdU26UWdBXgPYtlTN1LUiuXbboROhg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -408,9 +412,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.36.tgz",
|
||||
"integrity": "sha512-kkl6qmV0dTpyIMKagluzYqlc1vO0ecgpviK/7jwPbRDEv5fejRTaBBEE2KxEQbTHcLhiiDbhG7d5UybZWo/1zQ==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.42.tgz",
|
||||
"integrity": "sha512-ipiBdCA3ZjYgRfRLdQwP82rTiv/YVMtW36hTvAN5ZKAIfxBOyPXY7Cejp3bMXWgzKD8B6O+zoMzh01GZsCuEIA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -424,9 +428,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-arm64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-q8fY4r2Sx6P0Pr3VUm//eFYKVk07C5MHcEinU1BjyFnuYz4IxR/03uBbDwluR6ILIHnZTE7AkTUWIdidRi1Jjw==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.42.tgz",
|
||||
"integrity": "sha512-bU2tHRqTPOaoH/4m0zYHbFWpiYDmaA0gt90/3BMEFaM0PqVK/a6MA2V/ypV5PO0v8QxN6gH5hBPY4YJ2lopXgA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -440,9 +444,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-Hn8AYuxXXRptybPqoMkga4HRFE7/XmhtlQjXFHoAIhKUPPMeJH35GYEUWGbjteai9FLFvBAjEAlwEtSGxnqWww==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.42.tgz",
|
||||
"integrity": "sha512-75h1+22Ivy07+QvxHyhVqOdekupiTZVLN1PMwCDonAqyXd8TVNJfIRFrdL8QmSJrOJJ5h8H1I9ETyl2L8LQDaw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -456,9 +460,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-arm64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-S3C0attylLLRiCcHiJd036eDEMOY32+h8P+jJ3kTcfhJANNjP0TNBNL30TZmEdOSx/820HJFgRrqpNAvTbjnDA==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.42.tgz",
|
||||
"integrity": "sha512-W6Jebeu5TTDQMJUJVarEzRU9LlKpNkPBbjqSu+GUPTHDCly5zZEQq9uHkmHHl7OKm+mQ2zFySN83nmfCeZCyNA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -472,9 +476,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-32": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.36.tgz",
|
||||
"integrity": "sha512-Eh9OkyTrEZn9WGO4xkI3OPPpUX7p/3QYvdG0lL4rfr73Ap2HAr6D9lP59VMF64Ex01LhHSXwIsFG/8AQjh6eNw==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.42.tgz",
|
||||
"integrity": "sha512-Ooy/Bj+mJ1z4jlWcK5Dl6SlPlCgQB9zg1UrTCeY8XagvuWZ4qGPyYEWGkT94HUsRi2hKsXvcs6ThTOjBaJSMfg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -488,9 +492,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.36.tgz",
|
||||
"integrity": "sha512-vFVFS5ve7PuwlfgoWNyRccGDi2QTNkQo/2k5U5ttVD0jRFaMlc8UQee708fOZA6zTCDy5RWsT5MJw3sl2X6KDg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.42.tgz",
|
||||
"integrity": "sha512-2L0HbzQfbTuemUWfVqNIjOfaTRt9zsvjnme6lnr7/MO9toz/MJ5tZhjqrG6uDWDxhsaHI2/nsDgrv8uEEN2eoA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -504,9 +508,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.36.tgz",
|
||||
"integrity": "sha512-NhgU4n+NCsYgt7Hy61PCquEz5aevI6VjQvxwBxtxrooXsxt5b2xtOUXYZe04JxqQo+XZk3d1gcr7pbV9MAQ/Lg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.42.tgz",
|
||||
"integrity": "sha512-STq69yzCMhdRaWnh29UYrLSr/qaWMm/KqwaRF1pMEK7kDiagaXhSL1zQGXbYv94GuGY/zAwzK98+6idCMUOOCg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -520,9 +524,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-24Vq1M7FdpSmaTYuu1w0Hdhiqkbto1I5Pjyi+4Cdw5fJKGlwQuw+hWynTcRI/cOZxBcBpP21gND7W27gHAiftw==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.42.tgz",
|
||||
"integrity": "sha512-c3Ug3e9JpVr8jAcfbhirtpBauLxzYPpycjWulD71CF6ZSY26tvzmXMJYooQ2YKqDY4e/fPu5K8bm7MiXMnyxuA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -536,9 +540,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-mips64le": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.36.tgz",
|
||||
"integrity": "sha512-hZUeTXvppJN+5rEz2EjsOFM9F1bZt7/d2FUM1lmQo//rXh1RTFYzhC0txn7WV0/jCC7SvrGRaRz0NMsRPf8SIA==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.42.tgz",
|
||||
"integrity": "sha512-QuvpHGbYlkyXWf2cGm51LBCHx6eUakjaSrRpUqhPwjh/uvNUYvLmz2LgPTTPwCqaKt0iwL+OGVL0tXA5aDbAbg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -552,9 +556,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-ppc64le": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.36.tgz",
|
||||
"integrity": "sha512-1Bg3QgzZjO+QtPhP9VeIBhAduHEc2kzU43MzBnMwpLSZ890azr4/A9Dganun8nsqD/1TBcqhId0z4mFDO8FAvg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.42.tgz",
|
||||
"integrity": "sha512-8ohIVIWDbDT+i7lCx44YCyIRrOW1MYlks9fxTo0ME2LS/fxxdoJBwHWzaDYhjvf8kNpA+MInZvyOEAGoVDrMHg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -568,9 +572,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-riscv64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.36.tgz",
|
||||
"integrity": "sha512-dOE5pt3cOdqEhaufDRzNCHf5BSwxgygVak9UR7PH7KPVHwSTDAZHDoEjblxLqjJYpc5XaU9+gKJ9F8mp9r5I4A==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.42.tgz",
|
||||
"integrity": "sha512-DzDqK3TuoXktPyG1Lwx7vhaF49Onv3eR61KwQyxYo4y5UKTpL3NmuarHSIaSVlTFDDpcIajCDwz5/uwKLLgKiQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -584,9 +588,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-s390x": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.36.tgz",
|
||||
"integrity": "sha512-g4FMdh//BBGTfVHjF6MO7Cz8gqRoDPzXWxRvWkJoGroKA18G9m0wddvPbEqcQf5Tbt2vSc1CIgag7cXwTmoTXg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.42.tgz",
|
||||
"integrity": "sha512-YFRhPCxl8nb//Wn6SiS5pmtplBi4z9yC2gLrYoYI/tvwuB1jldir9r7JwAGy1Ck4D7sE7wBN9GFtUUX/DLdcEQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -600,9 +604,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-netbsd-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-UB2bVImxkWk4vjnP62ehFNZ73lQY1xcnL5ZNYF3x0AG+j8HgdkNF05v67YJdCIuUJpBuTyCK8LORCYo9onSW+A==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.42.tgz",
|
||||
"integrity": "sha512-QYSD2k+oT9dqB/4eEM9c+7KyNYsIPgzYOSrmfNGDIyJrbT1d+CFVKvnKahDKNJLfOYj8N4MgyFaU9/Ytc6w5Vw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -616,9 +620,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-openbsd-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-NvGB2Chf8GxuleXRGk8e9zD3aSdRO5kLt9coTQbCg7WMGXeX471sBgh4kSg8pjx0yTXRt0MlrUDnjVYnetyivg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.42.tgz",
|
||||
"integrity": "sha512-M2meNVIKWsm2HMY7+TU9AxM7ZVwI9havdsw6m/6EzdXysyCFFSoaTQ/Jg03izjCsK17FsVRHqRe26Llj6x0MNA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -632,9 +636,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.36.tgz",
|
||||
"integrity": "sha512-VkUZS5ftTSjhRjuRLp+v78auMO3PZBXu6xl4ajomGenEm2/rGuWlhFSjB7YbBNErOchj51Jb2OK8lKAo8qdmsQ==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.42.tgz",
|
||||
"integrity": "sha512-uXV8TAZEw36DkgW8Ak3MpSJs1ofBb3Smkc/6pZ29sCAN1KzCAQzsje4sUwugf+FVicrHvlamCOlFZIXgct+iqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -648,9 +652,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-32": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.36.tgz",
|
||||
"integrity": "sha512-bIar+A6hdytJjZrDxfMBUSEHHLfx3ynoEZXx/39nxy86pX/w249WZm8Bm0dtOAByAf4Z6qV0LsnTIJHiIqbw0w==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.42.tgz",
|
||||
"integrity": "sha512-4iw/8qWmRICWi9ZOnJJf9sYt6wmtp3hsN4TdI5NqgjfOkBVMxNdM9Vt3626G1Rda9ya2Q0hjQRD9W1o+m6Lz6g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -664,9 +668,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.36.tgz",
|
||||
"integrity": "sha512-+p4MuRZekVChAeueT1Y9LGkxrT5x7YYJxYE8ZOTcEfeUUN43vktSn6hUNsvxzzATrSgq5QqRdllkVBxWZg7KqQ==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.42.tgz",
|
||||
"integrity": "sha512-j3cdK+Y3+a5H0wHKmLGTJcq0+/2mMBHPWkItR3vytp/aUGD/ua/t2BLdfBIzbNN9nLCRL9sywCRpOpFMx3CxzA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -680,9 +684,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-arm64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-fBB4WlDqV1m18EF/aheGYQkQZHfPHiHJSBYzXIo8yKehek+0BtBwo/4PNwKGJ5T0YK0oc8pBKjgwPbzSrPLb+Q==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.42.tgz",
|
||||
"integrity": "sha512-+lRAARnF+hf8J0mN27ujO+VbhPbDqJ8rCcJKye4y7YZLV6C4n3pTRThAb388k/zqF5uM0lS5O201u0OqoWSicw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1127,9 +1131,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
|
||||
"integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz",
|
||||
"integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
|
||||
"dependencies": {
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
@@ -1199,13 +1203,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "12.3.2",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
|
||||
"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz",
|
||||
"integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "~2.1.0",
|
||||
"linkify-it": "^3.0.1",
|
||||
"entities": "~3.0.1",
|
||||
"linkify-it": "^4.0.1",
|
||||
"mdurl": "^1.0.1",
|
||||
"uc.micro": "^1.0.5"
|
||||
},
|
||||
@@ -1520,9 +1524,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.50.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
|
||||
"integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
|
||||
"version": "1.52.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.52.1.tgz",
|
||||
"integrity": "sha512-fSzYTbr7z8oQnVJ3Acp9hV80dM1fkMN7mSD/25mpcct9F7FPBMOI8krEYALgU1aZoqGhQNhTPsuSmxjnIvAm4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
@@ -1597,6 +1601,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/snabbdom": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.5.0.tgz",
|
||||
"integrity": "sha512-Ff5BKG18KrrPuskHJlA9aujPHqEabItaDl96l7ZZndF4zt5AYSczz7ZjjgQAX5IBd5cd25lw9NfgX21yVUJ+9g==",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
@@ -2002,9 +2014,9 @@
|
||||
}
|
||||
},
|
||||
"clipboard": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.10.tgz",
|
||||
"integrity": "sha512-cz3m2YVwFz95qSEbCDi2fzLN/epEN9zXBvfgAoGkvGOJZATMl9gtTDVOtBYkx2ODUJl2kvmud7n32sV2BpYR4g==",
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
|
||||
"integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
|
||||
"requires": {
|
||||
"good-listener": "^1.2.2",
|
||||
"select": "^1.1.2",
|
||||
@@ -2023,9 +2035,9 @@
|
||||
}
|
||||
},
|
||||
"codemirror": {
|
||||
"version": "5.65.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.2.tgz",
|
||||
"integrity": "sha512-SZM4Zq7XEC8Fhroqe3LxbEEX1zUPWH1wMr5zxiBuiUF64iYOUH/JI88v4tBag8MiBS8B8gRv8O1pPXGYXQ4ErA=="
|
||||
"version": "5.65.5",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.5.tgz",
|
||||
"integrity": "sha512-HNyhvGLnYz5c+kIsB9QKVitiZUevha3ovbIYaQiGzKo7ECSL/elWD9RXt3JgNr0NdnyqE9/Rc/7uLfkJQL638w=="
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
@@ -2093,9 +2105,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"entities": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
|
||||
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
|
||||
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q=="
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
@@ -2146,170 +2158,170 @@
|
||||
}
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.36.tgz",
|
||||
"integrity": "sha512-HhFHPiRXGYOCRlrhpiVDYKcFJRdO0sBElZ668M4lh2ER0YgnkLxECuFe7uWCf23FrcLc59Pqr7dHkTqmRPDHmw==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.42.tgz",
|
||||
"integrity": "sha512-V0uPZotCEHokJdNqyozH6qsaQXqmZEOiZWrXnds/zaH/0SyrIayRXWRB98CENO73MIZ9T3HBIOsmds5twWtmgw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild-android-64": "0.14.36",
|
||||
"esbuild-android-arm64": "0.14.36",
|
||||
"esbuild-darwin-64": "0.14.36",
|
||||
"esbuild-darwin-arm64": "0.14.36",
|
||||
"esbuild-freebsd-64": "0.14.36",
|
||||
"esbuild-freebsd-arm64": "0.14.36",
|
||||
"esbuild-linux-32": "0.14.36",
|
||||
"esbuild-linux-64": "0.14.36",
|
||||
"esbuild-linux-arm": "0.14.36",
|
||||
"esbuild-linux-arm64": "0.14.36",
|
||||
"esbuild-linux-mips64le": "0.14.36",
|
||||
"esbuild-linux-ppc64le": "0.14.36",
|
||||
"esbuild-linux-riscv64": "0.14.36",
|
||||
"esbuild-linux-s390x": "0.14.36",
|
||||
"esbuild-netbsd-64": "0.14.36",
|
||||
"esbuild-openbsd-64": "0.14.36",
|
||||
"esbuild-sunos-64": "0.14.36",
|
||||
"esbuild-windows-32": "0.14.36",
|
||||
"esbuild-windows-64": "0.14.36",
|
||||
"esbuild-windows-arm64": "0.14.36"
|
||||
"esbuild-android-64": "0.14.42",
|
||||
"esbuild-android-arm64": "0.14.42",
|
||||
"esbuild-darwin-64": "0.14.42",
|
||||
"esbuild-darwin-arm64": "0.14.42",
|
||||
"esbuild-freebsd-64": "0.14.42",
|
||||
"esbuild-freebsd-arm64": "0.14.42",
|
||||
"esbuild-linux-32": "0.14.42",
|
||||
"esbuild-linux-64": "0.14.42",
|
||||
"esbuild-linux-arm": "0.14.42",
|
||||
"esbuild-linux-arm64": "0.14.42",
|
||||
"esbuild-linux-mips64le": "0.14.42",
|
||||
"esbuild-linux-ppc64le": "0.14.42",
|
||||
"esbuild-linux-riscv64": "0.14.42",
|
||||
"esbuild-linux-s390x": "0.14.42",
|
||||
"esbuild-netbsd-64": "0.14.42",
|
||||
"esbuild-openbsd-64": "0.14.42",
|
||||
"esbuild-sunos-64": "0.14.42",
|
||||
"esbuild-windows-32": "0.14.42",
|
||||
"esbuild-windows-64": "0.14.42",
|
||||
"esbuild-windows-arm64": "0.14.42"
|
||||
}
|
||||
},
|
||||
"esbuild-android-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.36.tgz",
|
||||
"integrity": "sha512-jwpBhF1jmo0tVCYC/ORzVN+hyVcNZUWuozGcLHfod0RJCedTDTvR4nwlTXdx1gtncDqjk33itjO+27OZHbiavw==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.42.tgz",
|
||||
"integrity": "sha512-P4Y36VUtRhK/zivqGVMqhptSrFILAGlYp0Z8r9UQqHJ3iWztRCNWnlBzD9HRx0DbueXikzOiwyOri+ojAFfW6A==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-android-arm64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-/hYkyFe7x7Yapmfv4X/tBmyKnggUmdQmlvZ8ZlBnV4+PjisrEhAvC3yWpURuD9XoB8Wa1d5dGkTsF53pIvpjsg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.42.tgz",
|
||||
"integrity": "sha512-0cOqCubq+RWScPqvtQdjXG3Czb3AWI2CaKw3HeXry2eoA2rrPr85HF7IpdU26UWdBXgPYtlTN1LUiuXbboROhg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.36.tgz",
|
||||
"integrity": "sha512-kkl6qmV0dTpyIMKagluzYqlc1vO0ecgpviK/7jwPbRDEv5fejRTaBBEE2KxEQbTHcLhiiDbhG7d5UybZWo/1zQ==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.42.tgz",
|
||||
"integrity": "sha512-ipiBdCA3ZjYgRfRLdQwP82rTiv/YVMtW36hTvAN5ZKAIfxBOyPXY7Cejp3bMXWgzKD8B6O+zoMzh01GZsCuEIA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-arm64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-q8fY4r2Sx6P0Pr3VUm//eFYKVk07C5MHcEinU1BjyFnuYz4IxR/03uBbDwluR6ILIHnZTE7AkTUWIdidRi1Jjw==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.42.tgz",
|
||||
"integrity": "sha512-bU2tHRqTPOaoH/4m0zYHbFWpiYDmaA0gt90/3BMEFaM0PqVK/a6MA2V/ypV5PO0v8QxN6gH5hBPY4YJ2lopXgA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-Hn8AYuxXXRptybPqoMkga4HRFE7/XmhtlQjXFHoAIhKUPPMeJH35GYEUWGbjteai9FLFvBAjEAlwEtSGxnqWww==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.42.tgz",
|
||||
"integrity": "sha512-75h1+22Ivy07+QvxHyhVqOdekupiTZVLN1PMwCDonAqyXd8TVNJfIRFrdL8QmSJrOJJ5h8H1I9ETyl2L8LQDaw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-arm64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-S3C0attylLLRiCcHiJd036eDEMOY32+h8P+jJ3kTcfhJANNjP0TNBNL30TZmEdOSx/820HJFgRrqpNAvTbjnDA==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.42.tgz",
|
||||
"integrity": "sha512-W6Jebeu5TTDQMJUJVarEzRU9LlKpNkPBbjqSu+GUPTHDCly5zZEQq9uHkmHHl7OKm+mQ2zFySN83nmfCeZCyNA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-32": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.36.tgz",
|
||||
"integrity": "sha512-Eh9OkyTrEZn9WGO4xkI3OPPpUX7p/3QYvdG0lL4rfr73Ap2HAr6D9lP59VMF64Ex01LhHSXwIsFG/8AQjh6eNw==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.42.tgz",
|
||||
"integrity": "sha512-Ooy/Bj+mJ1z4jlWcK5Dl6SlPlCgQB9zg1UrTCeY8XagvuWZ4qGPyYEWGkT94HUsRi2hKsXvcs6ThTOjBaJSMfg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.36.tgz",
|
||||
"integrity": "sha512-vFVFS5ve7PuwlfgoWNyRccGDi2QTNkQo/2k5U5ttVD0jRFaMlc8UQee708fOZA6zTCDy5RWsT5MJw3sl2X6KDg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.42.tgz",
|
||||
"integrity": "sha512-2L0HbzQfbTuemUWfVqNIjOfaTRt9zsvjnme6lnr7/MO9toz/MJ5tZhjqrG6uDWDxhsaHI2/nsDgrv8uEEN2eoA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.36.tgz",
|
||||
"integrity": "sha512-NhgU4n+NCsYgt7Hy61PCquEz5aevI6VjQvxwBxtxrooXsxt5b2xtOUXYZe04JxqQo+XZk3d1gcr7pbV9MAQ/Lg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.42.tgz",
|
||||
"integrity": "sha512-STq69yzCMhdRaWnh29UYrLSr/qaWMm/KqwaRF1pMEK7kDiagaXhSL1zQGXbYv94GuGY/zAwzK98+6idCMUOOCg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-24Vq1M7FdpSmaTYuu1w0Hdhiqkbto1I5Pjyi+4Cdw5fJKGlwQuw+hWynTcRI/cOZxBcBpP21gND7W27gHAiftw==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.42.tgz",
|
||||
"integrity": "sha512-c3Ug3e9JpVr8jAcfbhirtpBauLxzYPpycjWulD71CF6ZSY26tvzmXMJYooQ2YKqDY4e/fPu5K8bm7MiXMnyxuA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-mips64le": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.36.tgz",
|
||||
"integrity": "sha512-hZUeTXvppJN+5rEz2EjsOFM9F1bZt7/d2FUM1lmQo//rXh1RTFYzhC0txn7WV0/jCC7SvrGRaRz0NMsRPf8SIA==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.42.tgz",
|
||||
"integrity": "sha512-QuvpHGbYlkyXWf2cGm51LBCHx6eUakjaSrRpUqhPwjh/uvNUYvLmz2LgPTTPwCqaKt0iwL+OGVL0tXA5aDbAbg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-ppc64le": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.36.tgz",
|
||||
"integrity": "sha512-1Bg3QgzZjO+QtPhP9VeIBhAduHEc2kzU43MzBnMwpLSZ890azr4/A9Dganun8nsqD/1TBcqhId0z4mFDO8FAvg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.42.tgz",
|
||||
"integrity": "sha512-8ohIVIWDbDT+i7lCx44YCyIRrOW1MYlks9fxTo0ME2LS/fxxdoJBwHWzaDYhjvf8kNpA+MInZvyOEAGoVDrMHg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-riscv64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.36.tgz",
|
||||
"integrity": "sha512-dOE5pt3cOdqEhaufDRzNCHf5BSwxgygVak9UR7PH7KPVHwSTDAZHDoEjblxLqjJYpc5XaU9+gKJ9F8mp9r5I4A==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.42.tgz",
|
||||
"integrity": "sha512-DzDqK3TuoXktPyG1Lwx7vhaF49Onv3eR61KwQyxYo4y5UKTpL3NmuarHSIaSVlTFDDpcIajCDwz5/uwKLLgKiQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-s390x": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.36.tgz",
|
||||
"integrity": "sha512-g4FMdh//BBGTfVHjF6MO7Cz8gqRoDPzXWxRvWkJoGroKA18G9m0wddvPbEqcQf5Tbt2vSc1CIgag7cXwTmoTXg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.42.tgz",
|
||||
"integrity": "sha512-YFRhPCxl8nb//Wn6SiS5pmtplBi4z9yC2gLrYoYI/tvwuB1jldir9r7JwAGy1Ck4D7sE7wBN9GFtUUX/DLdcEQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-netbsd-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-UB2bVImxkWk4vjnP62ehFNZ73lQY1xcnL5ZNYF3x0AG+j8HgdkNF05v67YJdCIuUJpBuTyCK8LORCYo9onSW+A==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.42.tgz",
|
||||
"integrity": "sha512-QYSD2k+oT9dqB/4eEM9c+7KyNYsIPgzYOSrmfNGDIyJrbT1d+CFVKvnKahDKNJLfOYj8N4MgyFaU9/Ytc6w5Vw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-openbsd-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.36.tgz",
|
||||
"integrity": "sha512-NvGB2Chf8GxuleXRGk8e9zD3aSdRO5kLt9coTQbCg7WMGXeX471sBgh4kSg8pjx0yTXRt0MlrUDnjVYnetyivg==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.42.tgz",
|
||||
"integrity": "sha512-M2meNVIKWsm2HMY7+TU9AxM7ZVwI9havdsw6m/6EzdXysyCFFSoaTQ/Jg03izjCsK17FsVRHqRe26Llj6x0MNA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-sunos-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.36.tgz",
|
||||
"integrity": "sha512-VkUZS5ftTSjhRjuRLp+v78auMO3PZBXu6xl4ajomGenEm2/rGuWlhFSjB7YbBNErOchj51Jb2OK8lKAo8qdmsQ==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.42.tgz",
|
||||
"integrity": "sha512-uXV8TAZEw36DkgW8Ak3MpSJs1ofBb3Smkc/6pZ29sCAN1KzCAQzsje4sUwugf+FVicrHvlamCOlFZIXgct+iqQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-32": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.36.tgz",
|
||||
"integrity": "sha512-bIar+A6hdytJjZrDxfMBUSEHHLfx3ynoEZXx/39nxy86pX/w249WZm8Bm0dtOAByAf4Z6qV0LsnTIJHiIqbw0w==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.42.tgz",
|
||||
"integrity": "sha512-4iw/8qWmRICWi9ZOnJJf9sYt6wmtp3hsN4TdI5NqgjfOkBVMxNdM9Vt3626G1Rda9ya2Q0hjQRD9W1o+m6Lz6g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.36.tgz",
|
||||
"integrity": "sha512-+p4MuRZekVChAeueT1Y9LGkxrT5x7YYJxYE8ZOTcEfeUUN43vktSn6hUNsvxzzATrSgq5QqRdllkVBxWZg7KqQ==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.42.tgz",
|
||||
"integrity": "sha512-j3cdK+Y3+a5H0wHKmLGTJcq0+/2mMBHPWkItR3vytp/aUGD/ua/t2BLdfBIzbNN9nLCRL9sywCRpOpFMx3CxzA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-arm64": {
|
||||
"version": "0.14.36",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.36.tgz",
|
||||
"integrity": "sha512-fBB4WlDqV1m18EF/aheGYQkQZHfPHiHJSBYzXIo8yKehek+0BtBwo/4PNwKGJ5T0YK0oc8pBKjgwPbzSrPLb+Q==",
|
||||
"version": "0.14.42",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.42.tgz",
|
||||
"integrity": "sha512-+lRAARnF+hf8J0mN27ujO+VbhPbDqJ8rCcJKye4y7YZLV6C4n3pTRThAb388k/zqF5uM0lS5O201u0OqoWSicw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@@ -2615,9 +2627,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"linkify-it": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
|
||||
"integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz",
|
||||
"integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
|
||||
"requires": {
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
@@ -2675,13 +2687,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"markdown-it": {
|
||||
"version": "12.3.2",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
|
||||
"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz",
|
||||
"integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==",
|
||||
"requires": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "~2.1.0",
|
||||
"linkify-it": "^3.0.1",
|
||||
"entities": "~3.0.1",
|
||||
"linkify-it": "^4.0.1",
|
||||
"mdurl": "^1.0.1",
|
||||
"uc.micro": "^1.0.5"
|
||||
}
|
||||
@@ -2910,9 +2922,9 @@
|
||||
}
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.50.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
|
||||
"integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
|
||||
"version": "1.52.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.52.1.tgz",
|
||||
"integrity": "sha512-fSzYTbr7z8oQnVJ3Acp9hV80dM1fkMN7mSD/25mpcct9F7FPBMOI8krEYALgU1aZoqGhQNhTPsuSmxjnIvAm4Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
@@ -2969,6 +2981,11 @@
|
||||
"object-inspect": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"snabbdom": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.5.0.tgz",
|
||||
"integrity": "sha512-Ff5BKG18KrrPuskHJlA9aujPHqEabItaDl96l7ZZndF4zt5AYSczz7ZjjgQAX5IBd5cd25lw9NfgX21yVUJ+9g=="
|
||||
},
|
||||
"sortablejs": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
|
||||
15
package.json
15
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:css:dev": "sass ./resources/sass:./public/dist",
|
||||
"build:css:watch": "sass ./resources/sass:./public/dist --watch",
|
||||
"build:css:dev": "sass ./resources/sass:./public/dist --embed-sources",
|
||||
"build:css:watch": "sass ./resources/sass:./public/dist --watch --embed-sources",
|
||||
"build:css:production": "sass ./resources/sass:./public/dist -s compressed",
|
||||
"build:js:dev": "node dev/build/esbuild.js",
|
||||
"build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"",
|
||||
@@ -16,18 +16,19 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"chokidar-cli": "^3.0",
|
||||
"esbuild": "0.14.36",
|
||||
"esbuild": "0.14.42",
|
||||
"livereload": "^0.9.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"punycode": "^2.1.1",
|
||||
"sass": "^1.50.0"
|
||||
"sass": "^1.52.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"clipboard": "^2.0.10",
|
||||
"codemirror": "^5.65.2",
|
||||
"clipboard": "^2.0.11",
|
||||
"codemirror": "^5.65.5",
|
||||
"dropzone": "^5.9.3",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"snabbdom": "^3.5.0",
|
||||
"sortablejs": "^1.15.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<server name="MAIL_DRIVER" value="array"/>
|
||||
<server name="LOG_CHANNEL" value="single"/>
|
||||
<server name="AUTH_METHOD" value="standard"/>
|
||||
<server name="AUTH_AUTO_INITIATE" value="false"/>
|
||||
<server name="DISABLE_EXTERNAL_SERVICES" value="true"/>
|
||||
<server name="ALLOW_UNTRUSTED_SERVER_FETCHING" value="false"/>
|
||||
<server name="AVATAR_URL" value=""/>
|
||||
|
||||
54
public/dist/app.js
vendored
54
public/dist/app.js
vendored
File diff suppressed because one or more lines are too long
47
public/dist/code.js
vendored
47
public/dist/code.js
vendored
File diff suppressed because one or more lines are too long
2
public/dist/export-styles.css
vendored
2
public/dist/export-styles.css
vendored
File diff suppressed because one or more lines are too long
2
public/dist/styles.css
vendored
2
public/dist/styles.css
vendored
File diff suppressed because one or more lines are too long
1
resources/icons/download.svg
Normal file
1
resources/icons/download.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.59 9H15V4c0-.55-.45-1-1-1h-4c-.55 0-1 .45-1 1v5H7.41c-.89 0-1.34 1.08-.71 1.71l4.59 4.59c.39.39 1.02.39 1.41 0l4.59-4.59c.63-.63.19-1.71-.7-1.71zM5 19c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H6c-.55 0-1 .45-1 1z"/></svg>
|
||||
|
After Width: | Height: | Size: 297 B |
@@ -25,6 +25,7 @@ import 'codemirror/mode/ruby/ruby';
|
||||
import 'codemirror/mode/rust/rust';
|
||||
import 'codemirror/mode/shell/shell';
|
||||
import 'codemirror/mode/sql/sql';
|
||||
import 'codemirror/mode/stex/stex';
|
||||
import 'codemirror/mode/toml/toml';
|
||||
import 'codemirror/mode/vb/vb';
|
||||
import 'codemirror/mode/vbscript/vbscript';
|
||||
@@ -49,16 +50,19 @@ const modeMap = {
|
||||
diff: 'diff',
|
||||
for: 'fortran',
|
||||
fortran: 'fortran',
|
||||
'f#': 'text/x-fsharp',
|
||||
fsharp: 'text/x-fsharp',
|
||||
go: 'go',
|
||||
haskell: 'haskell',
|
||||
hs: 'haskell',
|
||||
html: 'htmlmixed',
|
||||
ini: 'properties',
|
||||
javascript: 'javascript',
|
||||
json: {name: 'javascript', json: true},
|
||||
js: 'javascript',
|
||||
jl: 'julia',
|
||||
julia: 'julia',
|
||||
javascript: 'text/javascript',
|
||||
json: 'application/json',
|
||||
js: 'text/javascript',
|
||||
jl: 'text/x-julia',
|
||||
julia: 'text/x-julia',
|
||||
latex: 'text/x-stex',
|
||||
lua: 'lua',
|
||||
md: 'markdown',
|
||||
mdown: 'markdown',
|
||||
@@ -69,7 +73,7 @@ const modeMap = {
|
||||
pl: 'perl',
|
||||
powershell: 'powershell',
|
||||
properties: 'properties',
|
||||
ocaml: 'mllike',
|
||||
ocaml: 'text/x-ocaml',
|
||||
pascal: 'text/x-pascal',
|
||||
pas: 'text/x-pascal',
|
||||
php: (content) => {
|
||||
@@ -83,8 +87,11 @@ const modeMap = {
|
||||
rs: 'rust',
|
||||
shell: 'shell',
|
||||
sh: 'shell',
|
||||
stext: 'text/x-stex',
|
||||
bash: 'shell',
|
||||
toml: 'toml',
|
||||
ts: 'text/typescript',
|
||||
typescript: 'text/typescript',
|
||||
sql: 'text/x-sql',
|
||||
vbs: 'vbscript',
|
||||
vbscript: 'vbscript',
|
||||
@@ -242,6 +249,21 @@ export function popupEditor(elem, modeSuggestion) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an inline editor to replace the given textarea.
|
||||
* @param {HTMLTextAreaElement} textArea
|
||||
* @param {String} mode
|
||||
* @returns {CodeMirror3}
|
||||
*/
|
||||
export function inlineEditor(textArea, mode) {
|
||||
return CodeMirror.fromTextArea(textArea, {
|
||||
mode: getMode(mode, textArea.value),
|
||||
lineNumbers: true,
|
||||
lineWrapping: false,
|
||||
theme: getTheme(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode of a codemirror instance.
|
||||
* @param cmInstance
|
||||
|
||||
37
resources/js/components/chapter-contents.js
Normal file
37
resources/js/components/chapter-contents.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import {slideUp, slideDown} from "../services/animations";
|
||||
|
||||
/**
|
||||
* @extends {Component}
|
||||
*/
|
||||
class ChapterContents {
|
||||
|
||||
setup() {
|
||||
this.list = this.$refs.list;
|
||||
this.toggle = this.$refs.toggle;
|
||||
|
||||
this.isOpen = this.toggle.classList.contains('open');
|
||||
this.toggle.addEventListener('click', this.click.bind(this));
|
||||
}
|
||||
|
||||
open() {
|
||||
this.toggle.classList.add('open');
|
||||
this.toggle.setAttribute('aria-expanded', 'true');
|
||||
slideDown(this.list, 180);
|
||||
this.isOpen = true;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.toggle.classList.remove('open');
|
||||
this.toggle.setAttribute('aria-expanded', 'false');
|
||||
slideUp(this.list, 180);
|
||||
this.isOpen = false;
|
||||
}
|
||||
|
||||
click(event) {
|
||||
event.preventDefault();
|
||||
this.isOpen ? this.close() : this.open();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ChapterContents;
|
||||
@@ -1,33 +0,0 @@
|
||||
import {slideUp, slideDown} from "../services/animations";
|
||||
|
||||
class ChapterToggle {
|
||||
|
||||
constructor(elem) {
|
||||
this.elem = elem;
|
||||
this.isOpen = elem.classList.contains('open');
|
||||
elem.addEventListener('click', this.click.bind(this));
|
||||
}
|
||||
|
||||
open() {
|
||||
const list = this.elem.parentNode.querySelector('.inset-list');
|
||||
this.elem.classList.add('open');
|
||||
this.elem.setAttribute('aria-expanded', 'true');
|
||||
slideDown(list, 240);
|
||||
}
|
||||
|
||||
close() {
|
||||
const list = this.elem.parentNode.querySelector('.inset-list');
|
||||
this.elem.classList.remove('open');
|
||||
this.elem.setAttribute('aria-expanded', 'false');
|
||||
slideUp(list, 240);
|
||||
}
|
||||
|
||||
click(event) {
|
||||
event.preventDefault();
|
||||
this.isOpen ? this.close() : this.open();
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ChapterToggle;
|
||||
@@ -33,10 +33,11 @@ class CodeEditor {
|
||||
onSelect(this.languageLinks, event => {
|
||||
const language = event.target.dataset.lang;
|
||||
this.languageInput.value = language;
|
||||
this.updateEditorMode(language);
|
||||
this.languageInputChange(language);
|
||||
});
|
||||
|
||||
onEnterPress(this.languageInput, e => this.save());
|
||||
this.languageInput.addEventListener('input', e => this.languageInputChange(this.languageInput.value));
|
||||
onSelect(this.saveButton, e => this.save());
|
||||
|
||||
onChildEvent(this.historyList, 'button', 'click', (event, elem) => {
|
||||
@@ -60,7 +61,7 @@ class CodeEditor {
|
||||
this.callback = callback;
|
||||
|
||||
this.show()
|
||||
.then(() => this.updateEditorMode(language))
|
||||
.then(() => this.languageInputChange(language))
|
||||
.then(() => window.importVersioned('code'))
|
||||
.then(Code => Code.setContent(this.editor, code));
|
||||
}
|
||||
@@ -90,6 +91,22 @@ class CodeEditor {
|
||||
Code.setMode(this.editor, language, this.editor.getValue());
|
||||
}
|
||||
|
||||
languageInputChange(language) {
|
||||
this.updateEditorMode(language);
|
||||
const inputLang = language.toLowerCase();
|
||||
let matched = false;
|
||||
|
||||
for (const link of this.languageLinks) {
|
||||
const lang = link.dataset.lang.toLowerCase().trim();
|
||||
const isMatch = inputLang && lang.startsWith(inputLang);
|
||||
link.classList.toggle('active', isMatch);
|
||||
if (isMatch && !matched) {
|
||||
link.scrollIntoView({block: "center", behavior: "smooth"});
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadHistory() {
|
||||
this.history = JSON.parse(window.sessionStorage.getItem(this.historyKey) || '{}');
|
||||
const historyKeys = Object.keys(this.history).reverse();
|
||||
|
||||
16
resources/js/components/code-textarea.js
Normal file
16
resources/js/components/code-textarea.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* A simple component to render a code editor within the textarea
|
||||
* this exists upon.
|
||||
* @extends {Component}
|
||||
*/
|
||||
class CodeTextarea {
|
||||
|
||||
async setup() {
|
||||
const mode = this.$opts.mode;
|
||||
const Code = await window.importVersioned('code');
|
||||
Code.inlineEditor(this.$el, mode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CodeTextarea;
|
||||
@@ -1,4 +1,5 @@
|
||||
import {debounce} from "../services/util";
|
||||
import {transitionHeight} from "../services/animations";
|
||||
|
||||
class DropdownSearch {
|
||||
|
||||
@@ -51,7 +52,9 @@ class DropdownSearch {
|
||||
|
||||
try {
|
||||
const resp = await window.$http.get(this.getAjaxUrl(searchTerm));
|
||||
const animate = transitionHeight(this.listContainerElem, 80);
|
||||
this.listContainerElem.innerHTML = resp.data;
|
||||
animate();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
@@ -28,18 +28,31 @@ class DropDown {
|
||||
this.menu.classList.add('anim', 'menuIn');
|
||||
this.toggle.setAttribute('aria-expanded', 'true');
|
||||
|
||||
const menuOriginalRect = this.menu.getBoundingClientRect();
|
||||
let heightOffset = 0;
|
||||
const toggleHeight = this.toggle.getBoundingClientRect().height;
|
||||
const dropUpwards = menuOriginalRect.bottom > window.innerHeight;
|
||||
|
||||
// If enabled, Move to body to prevent being trapped within scrollable sections
|
||||
if (this.moveMenu) {
|
||||
// Move to body to prevent being trapped within scrollable sections
|
||||
this.rect = this.menu.getBoundingClientRect();
|
||||
this.body.appendChild(this.menu);
|
||||
this.menu.style.position = 'fixed';
|
||||
if (this.direction === 'right') {
|
||||
this.menu.style.right = `${(this.rect.right - this.rect.width)}px`;
|
||||
this.menu.style.right = `${(menuOriginalRect.right - menuOriginalRect.width)}px`;
|
||||
} else {
|
||||
this.menu.style.left = `${this.rect.left}px`;
|
||||
this.menu.style.left = `${menuOriginalRect.left}px`;
|
||||
}
|
||||
this.menu.style.top = `${this.rect.top}px`;
|
||||
this.menu.style.width = `${this.rect.width}px`;
|
||||
this.menu.style.width = `${menuOriginalRect.width}px`;
|
||||
heightOffset = dropUpwards ? (window.innerHeight - menuOriginalRect.top - toggleHeight / 2) : menuOriginalRect.top;
|
||||
}
|
||||
|
||||
// Adjust menu to display upwards if near the bottom of the screen
|
||||
if (dropUpwards) {
|
||||
this.menu.style.top = 'initial';
|
||||
this.menu.style.bottom = `${heightOffset}px`;
|
||||
} else {
|
||||
this.menu.style.top = `${heightOffset}px`;
|
||||
this.menu.style.bottom = 'initial';
|
||||
}
|
||||
|
||||
// Set listener to hide on mouse leave or window click
|
||||
@@ -74,18 +87,21 @@ class DropDown {
|
||||
this.menu.style.display = 'none';
|
||||
this.menu.classList.remove('anim', 'menuIn');
|
||||
this.toggle.setAttribute('aria-expanded', 'false');
|
||||
this.menu.style.top = '';
|
||||
this.menu.style.bottom = '';
|
||||
|
||||
if (this.moveMenu) {
|
||||
this.menu.style.position = '';
|
||||
this.menu.style[this.direction] = '';
|
||||
this.menu.style.top = '';
|
||||
this.menu.style.width = '';
|
||||
this.container.appendChild(this.menu);
|
||||
}
|
||||
|
||||
this.showing = false;
|
||||
}
|
||||
|
||||
getFocusable() {
|
||||
return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
|
||||
return Array.from(this.menu.querySelectorAll('[tabindex]:not([tabindex="-1"]),[href],button,input:not([type=hidden])'));
|
||||
}
|
||||
|
||||
focusNext() {
|
||||
|
||||
@@ -7,36 +7,34 @@ class EntitySelectorPopup {
|
||||
setup() {
|
||||
this.elem = this.$el;
|
||||
this.selectButton = this.$refs.select;
|
||||
this.searchInput = this.$refs.searchInput;
|
||||
|
||||
window.EntitySelectorPopup = this;
|
||||
this.selectorEl = this.$refs.selector;
|
||||
|
||||
this.callback = null;
|
||||
this.selection = null;
|
||||
|
||||
this.selectButton.addEventListener('click', this.onSelectButtonClick.bind(this));
|
||||
window.$events.listen('entity-select-change', this.onSelectionChange.bind(this));
|
||||
window.$events.listen('entity-select-confirm', this.onSelectionConfirm.bind(this));
|
||||
window.$events.listen('entity-select-confirm', this.handleConfirmedSelection.bind(this));
|
||||
}
|
||||
|
||||
show(callback) {
|
||||
this.callback = callback;
|
||||
this.elem.components.popup.show();
|
||||
this.searchInput.focus();
|
||||
this.getSelector().focusSearch();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.elem.components.popup.hide();
|
||||
}
|
||||
|
||||
onSelectButtonClick() {
|
||||
this.hide();
|
||||
if (this.selection !== null && this.callback) this.callback(this.selection);
|
||||
getSelector() {
|
||||
return this.selectorEl.components['entity-selector'];
|
||||
}
|
||||
|
||||
onSelectionConfirm(entity) {
|
||||
this.hide();
|
||||
if (this.callback && entity) this.callback(entity);
|
||||
onSelectButtonClick() {
|
||||
this.handleConfirmedSelection(this.selection);
|
||||
}
|
||||
|
||||
onSelectionChange(entity) {
|
||||
@@ -47,6 +45,12 @@ class EntitySelectorPopup {
|
||||
this.selectButton.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
handleConfirmedSelection(entity) {
|
||||
this.hide();
|
||||
this.getSelector().reset();
|
||||
if (this.callback && entity) this.callback(entity);
|
||||
}
|
||||
}
|
||||
|
||||
export default EntitySelectorPopup;
|
||||
@@ -87,6 +87,16 @@ class EntitySelector {
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.searchInput.value = '';
|
||||
this.showLoading();
|
||||
this.initialLoad();
|
||||
}
|
||||
|
||||
focusSearch() {
|
||||
this.searchInput.focus();
|
||||
}
|
||||
|
||||
showLoading() {
|
||||
this.loading.style.display = 'block';
|
||||
this.resultsContainer.style.display = 'none';
|
||||
|
||||
@@ -6,9 +6,10 @@ import attachmentsList from "./attachments-list.js"
|
||||
import autoSuggest from "./auto-suggest.js"
|
||||
import backToTop from "./back-to-top.js"
|
||||
import bookSort from "./book-sort.js"
|
||||
import chapterToggle from "./chapter-toggle.js"
|
||||
import chapterContents from "./chapter-contents.js"
|
||||
import codeEditor from "./code-editor.js"
|
||||
import codeHighlighter from "./code-highlighter.js"
|
||||
import codeTextarea from "./code-textarea.js"
|
||||
import collapsible from "./collapsible.js"
|
||||
import confirmDialog from "./confirm-dialog"
|
||||
import customCheckbox from "./custom-checkbox.js"
|
||||
@@ -62,9 +63,10 @@ const componentMapping = {
|
||||
"auto-suggest": autoSuggest,
|
||||
"back-to-top": backToTop,
|
||||
"book-sort": bookSort,
|
||||
"chapter-toggle": chapterToggle,
|
||||
"chapter-contents": chapterContents,
|
||||
"code-editor": codeEditor,
|
||||
"code-highlighter": codeHighlighter,
|
||||
"code-textarea": codeTextarea,
|
||||
"collapsible": collapsible,
|
||||
"confirm-dialog": confirmDialog,
|
||||
"custom-checkbox": customCheckbox,
|
||||
|
||||
@@ -2,7 +2,7 @@ import MarkdownIt from "markdown-it";
|
||||
import mdTasksLists from 'markdown-it-task-lists';
|
||||
import Clipboard from "../services/clipboard";
|
||||
import {debounce} from "../services/util";
|
||||
|
||||
import {patchDomFromHtmlString} from "../services/vdom";
|
||||
import DrawIO from "../services/drawio";
|
||||
|
||||
class MarkdownEditor {
|
||||
@@ -127,18 +127,31 @@ class MarkdownEditor {
|
||||
updateAndRender() {
|
||||
const content = this.cm.getValue();
|
||||
this.input.value = content;
|
||||
|
||||
const html = this.markdown.render(content);
|
||||
window.$events.emit('editor-html-change', html);
|
||||
window.$events.emit('editor-markdown-change', content);
|
||||
|
||||
// Set body content
|
||||
const target = this.getDisplayTarget();
|
||||
this.displayDoc.body.className = 'page-content';
|
||||
this.displayDoc.body.innerHTML = html;
|
||||
patchDomFromHtmlString(target, html);
|
||||
|
||||
// Copy styles from page head and set custom styles for editor
|
||||
this.loadStylesIntoDisplay();
|
||||
}
|
||||
|
||||
getDisplayTarget() {
|
||||
const body = this.displayDoc.body;
|
||||
|
||||
if (body.children.length === 0) {
|
||||
const wrap = document.createElement('div');
|
||||
this.displayDoc.body.append(wrap);
|
||||
}
|
||||
|
||||
return body.children[0];
|
||||
}
|
||||
|
||||
loadStylesIntoDisplay() {
|
||||
if (this.displayStylesLoaded) return;
|
||||
this.displayDoc.documentElement.classList.add('markdown-editor-display');
|
||||
|
||||
@@ -49,7 +49,7 @@ export function slideUp(element, animTime = 400) {
|
||||
const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
|
||||
const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
|
||||
const animStyles = {
|
||||
height: [`${currentHeight}px`, '0px'],
|
||||
maxHeight: [`${currentHeight}px`, '0px'],
|
||||
overflow: ['hidden', 'hidden'],
|
||||
paddingTop: [currentPaddingTop, '0px'],
|
||||
paddingBottom: [currentPaddingBottom, '0px'],
|
||||
@@ -73,7 +73,7 @@ export function slideDown(element, animTime = 400) {
|
||||
const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
|
||||
const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
|
||||
const animStyles = {
|
||||
height: ['0px', `${targetHeight}px`],
|
||||
maxHeight: ['0px', `${targetHeight}px`],
|
||||
overflow: ['hidden', 'hidden'],
|
||||
paddingTop: ['0px', targetPaddingTop],
|
||||
paddingBottom: ['0px', targetPaddingBottom],
|
||||
@@ -82,6 +82,38 @@ export function slideDown(element, animTime = 400) {
|
||||
animateStyles(element, animStyles, animTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition the height of the given element between two states.
|
||||
* Call with first state, and you'll receive a function in return.
|
||||
* Call the returned function in the second state to animate between those two states.
|
||||
* If animating to/from 0-height use the slide-up/slide down as easier alternatives.
|
||||
* @param {Element} element - Element to animate
|
||||
* @param {Number} animTime - Animation time in ms
|
||||
* @returns {function} - Function to run in second state to trigger animation.
|
||||
*/
|
||||
export function transitionHeight(element, animTime = 400) {
|
||||
const startHeight = element.getBoundingClientRect().height;
|
||||
const initialComputedStyles = getComputedStyle(element);
|
||||
const startPaddingTop = initialComputedStyles.getPropertyValue('padding-top');
|
||||
const startPaddingBottom = initialComputedStyles.getPropertyValue('padding-bottom');
|
||||
|
||||
return () => {
|
||||
cleanupExistingElementAnimation(element);
|
||||
const targetHeight = element.getBoundingClientRect().height;
|
||||
const computedStyles = getComputedStyle(element);
|
||||
const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
|
||||
const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
|
||||
const animStyles = {
|
||||
height: [`${startHeight}px`, `${targetHeight}px`],
|
||||
overflow: ['hidden', 'hidden'],
|
||||
paddingTop: [startPaddingTop, targetPaddingTop],
|
||||
paddingBottom: [startPaddingBottom, targetPaddingBottom],
|
||||
};
|
||||
|
||||
animateStyles(element, animStyles, animTime);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the css styles of an element using FLIP animation techniques.
|
||||
* Styles must be an object where the keys are style properties, camelcase, and the values
|
||||
|
||||
31
resources/js/services/vdom.js
Normal file
31
resources/js/services/vdom.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
init,
|
||||
attributesModule,
|
||||
toVNode
|
||||
} from "snabbdom";
|
||||
|
||||
let patcher;
|
||||
|
||||
/**
|
||||
* @returns {Function}
|
||||
*/
|
||||
function getPatcher() {
|
||||
if (patcher) return patcher;
|
||||
|
||||
|
||||
patcher = init([
|
||||
attributesModule,
|
||||
]);
|
||||
|
||||
return patcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Element} domTarget
|
||||
* @param {String} html
|
||||
*/
|
||||
export function patchDomFromHtmlString(domTarget, html) {
|
||||
const contentDom = document.createElement('div');
|
||||
contentDom.innerHTML = html;
|
||||
getPatcher()(toVNode(domTarget), toVNode(contentDom));
|
||||
}
|
||||
@@ -60,13 +60,7 @@ async function uploadImageFile(file, pageId) {
|
||||
throw new Error(`Not an image file`);
|
||||
}
|
||||
|
||||
let ext = 'png';
|
||||
if (file.name) {
|
||||
let fileNameMatches = file.name.match(/\.(.+)$/);
|
||||
if (fileNameMatches.length > 1) ext = fileNameMatches[1];
|
||||
}
|
||||
|
||||
const remoteFilename = "image-" + Date.now() + "." + ext;
|
||||
const remoteFilename = file.name || `image-${Date.now()}.png`;
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, remoteFilename);
|
||||
formData.append('uploaded_to', pageId);
|
||||
|
||||
@@ -86,7 +86,13 @@ function defineCodeBlockCustomElement(editor) {
|
||||
getContent() {
|
||||
const code = this.querySelector('code') || this.querySelector('pre');
|
||||
const tempEl = document.createElement('pre');
|
||||
tempEl.innerHTML = code.innerHTML.replace().replace(/<br\s*[\/]?>/gi ,'\n').replace(/\ufeff/g, '');
|
||||
tempEl.innerHTML = code.innerHTML.replace(/\ufeff/g, '');
|
||||
|
||||
const brs = tempEl.querySelectorAll('br');
|
||||
for (const br of brs) {
|
||||
br.replaceWith('\n');
|
||||
}
|
||||
|
||||
return tempEl.textContent;
|
||||
}
|
||||
|
||||
@@ -104,6 +110,7 @@ function defineCodeBlockCustomElement(editor) {
|
||||
|
||||
const container = this.shadowRoot.querySelector('.CodeMirrorContainer');
|
||||
const renderCodeMirror = (Code) => {
|
||||
console.log({content});
|
||||
this.cm = Code.wysiwygView(container, content, this.getLanguage());
|
||||
Code.updateLayout(this.cm);
|
||||
setTimeout(() => {
|
||||
@@ -159,6 +166,7 @@ function register(editor, url) {
|
||||
showPopup(editor, textContent, '', (newCode, newLang) => {
|
||||
const pre = doc.createElement('pre');
|
||||
const code = doc.createElement('code');
|
||||
console.log(newCode);
|
||||
code.classList.add(`language-${newLang}`);
|
||||
code.innerText = newCode;
|
||||
pre.append(code);
|
||||
|
||||
@@ -7,61 +7,65 @@ return [
|
||||
|
||||
// Pages
|
||||
'page_create' => 'تم إنشاء صفحة',
|
||||
'page_create_notification' => 'Page successfully created',
|
||||
'page_create_notification' => 'تم إنشاء الصفحة بنجاح',
|
||||
'page_update' => 'تم تحديث الصفحة',
|
||||
'page_update_notification' => 'Page successfully updated',
|
||||
'page_update_notification' => 'تم تحديث الصفحة بنجاح',
|
||||
'page_delete' => 'تم حذف الصفحة',
|
||||
'page_delete_notification' => 'Page successfully deleted',
|
||||
'page_delete_notification' => 'تم حذف الصفحة بنجاح',
|
||||
'page_restore' => 'تمت استعادة الصفحة',
|
||||
'page_restore_notification' => 'Page successfully restored',
|
||||
'page_restore_notification' => 'تمت استعادة الصفحة بنجاح',
|
||||
'page_move' => 'تم نقل الصفحة',
|
||||
|
||||
// Chapters
|
||||
'chapter_create' => 'تم إنشاء فصل',
|
||||
'chapter_create_notification' => 'Chapter successfully created',
|
||||
'chapter_create_notification' => 'تم إنشاء الفصل بنجاح',
|
||||
'chapter_update' => 'تم تحديث الفصل',
|
||||
'chapter_update_notification' => 'Chapter successfully updated',
|
||||
'chapter_update_notification' => 'تم تحديث الفصل بنجاح',
|
||||
'chapter_delete' => 'تم حذف الفصل',
|
||||
'chapter_delete_notification' => 'Chapter successfully deleted',
|
||||
'chapter_delete_notification' => 'تم حذف الفصل بنجاح',
|
||||
'chapter_move' => 'تم نقل الفصل',
|
||||
|
||||
// Books
|
||||
'book_create' => 'تم إنشاء كتاب',
|
||||
'book_create_notification' => 'Book successfully created',
|
||||
'book_create_notification' => 'تم إنشاء الكتاب بنجاح',
|
||||
'book_create_from_chapter' => 'converted chapter to book',
|
||||
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
|
||||
'book_update' => 'تم تحديث الكتاب',
|
||||
'book_update_notification' => 'Book successfully updated',
|
||||
'book_update_notification' => 'تم تحديث الكتاب بنجاح',
|
||||
'book_delete' => 'تم حذف الكتاب',
|
||||
'book_delete_notification' => 'Book successfully deleted',
|
||||
'book_delete_notification' => 'تم حذف الكتاب بنجاح',
|
||||
'book_sort' => 'تم سرد الكتاب',
|
||||
'book_sort_notification' => 'Book successfully re-sorted',
|
||||
'book_sort_notification' => 'تم إعادة فرز الكتاب بنجاح',
|
||||
|
||||
// Bookshelves
|
||||
'bookshelf_create' => 'created bookshelf',
|
||||
'bookshelf_create_notification' => 'Bookshelf successfully created',
|
||||
'bookshelf_create' => 'تم إنشاء رف كتب',
|
||||
'bookshelf_create_notification' => 'تم إنشاء الرف بنجاح',
|
||||
'bookshelf_create_from_book' => 'converted book to bookshelf',
|
||||
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
|
||||
'bookshelf_update' => 'تم تحديث الرف',
|
||||
'bookshelf_update_notification' => 'Bookshelf successfully updated',
|
||||
'bookshelf_update_notification' => 'تم تحديث الرف بنجاح',
|
||||
'bookshelf_delete' => 'تم تحديث الرف',
|
||||
'bookshelf_delete_notification' => 'Bookshelf successfully deleted',
|
||||
'bookshelf_delete_notification' => 'تم حذف الرف بنجاح',
|
||||
|
||||
// Favourites
|
||||
'favourite_add_notification' => '":name" has been added to your favourites',
|
||||
'favourite_remove_notification' => '":name" has been removed from your favourites',
|
||||
'favourite_add_notification' => 'تم إضافة ":name" إلى المفضلة لديك',
|
||||
'favourite_remove_notification' => 'تم إزالة ":name" من المفضلة لديك',
|
||||
|
||||
// MFA
|
||||
'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
|
||||
'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
|
||||
'mfa_setup_method_notification' => 'تم تكوين طريقة متعددة العوامل بنجاح',
|
||||
'mfa_remove_method_notification' => 'تمت إزالة طريقة متعددة العوامل بنجاح',
|
||||
|
||||
// Webhooks
|
||||
'webhook_create' => 'created webhook',
|
||||
'webhook_create_notification' => 'Webhook successfully created',
|
||||
'webhook_update' => 'updated webhook',
|
||||
'webhook_update_notification' => 'Webhook successfully updated',
|
||||
'webhook_delete' => 'deleted webhook',
|
||||
'webhook_delete_notification' => 'Webhook successfully deleted',
|
||||
'webhook_create' => 'تم إنشاء webhook',
|
||||
'webhook_create_notification' => 'تم إنشاء Webhook بنجاح',
|
||||
'webhook_update' => 'تم تحديث webhook',
|
||||
'webhook_update_notification' => 'تم تحديث Webhook بنجاح',
|
||||
'webhook_delete' => 'حذف webhook',
|
||||
'webhook_delete_notification' => 'تم حذف Webhook بنجاح',
|
||||
|
||||
// Users
|
||||
'user_update_notification' => 'User successfully updated',
|
||||
'user_delete_notification' => 'User successfully removed',
|
||||
'user_update_notification' => 'تم تحديث المستخدم بنجاح',
|
||||
'user_delete_notification' => 'تم إزالة المستخدم بنجاح',
|
||||
|
||||
// Other
|
||||
'commented_on' => 'تم التعليق',
|
||||
|
||||
@@ -38,6 +38,11 @@ return [
|
||||
'registration_email_domain_invalid' => 'المجال الخاص بالبريد الإلكتروني لا يملك حق الوصول لهذا التطبيق',
|
||||
'register_success' => 'شكراً لإنشاء حسابكم! تم تسجيلكم ودخولكم للحساب الخاص بكم.',
|
||||
|
||||
// Login auto-initiation
|
||||
'auto_init_starting' => 'Attempting Login',
|
||||
'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.',
|
||||
'auto_init_start_link' => 'Proceed with authentication',
|
||||
|
||||
// Password Reset
|
||||
'reset_password' => 'استعادة كلمة المرور',
|
||||
'reset_password_send_instructions' => 'أدخل بريدك الإلكتروني بالأسفل وسيتم إرسال رسالة برابط لاستعادة كلمة المرور.',
|
||||
|
||||
@@ -47,6 +47,8 @@ return [
|
||||
'previous' => 'Previous',
|
||||
'filter_active' => 'Active Filter:',
|
||||
'filter_clear' => 'Clear Filter',
|
||||
'download' => 'Download',
|
||||
'open_in_tab' => 'Open in Tab',
|
||||
|
||||
// Sort Options
|
||||
'sort_options' => 'خيارات الفرز',
|
||||
|
||||
@@ -355,4 +355,16 @@ return [
|
||||
'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.',
|
||||
'copy_consider_attachments' => 'Page attachments will not be copied.',
|
||||
'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.',
|
||||
|
||||
// Conversions
|
||||
'convert_to_shelf' => 'Convert to Shelf',
|
||||
'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.',
|
||||
'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.',
|
||||
'convert_book' => 'Convert Book',
|
||||
'convert_book_confirm' => 'Are you sure you want to convert this book?',
|
||||
'convert_undo_warning' => 'This cannot be as easily undone.',
|
||||
'convert_to_book' => 'Convert to Book',
|
||||
'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.',
|
||||
'convert_chapter' => 'Convert Chapter',
|
||||
'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?',
|
||||
];
|
||||
|
||||
@@ -279,6 +279,7 @@ return [
|
||||
'es_AR' => 'Español Argentina',
|
||||
'et' => 'Eesti keel',
|
||||
'eu' => 'Euskara',
|
||||
'fa' => 'فارسی',
|
||||
'fr' => 'Français',
|
||||
'he' => 'עברית',
|
||||
'hr' => 'Hrvatski',
|
||||
|
||||
@@ -28,6 +28,8 @@ return [
|
||||
// Books
|
||||
'book_create' => 'създадена книга',
|
||||
'book_create_notification' => 'Книгата е създадена успешно',
|
||||
'book_create_from_chapter' => 'converted chapter to book',
|
||||
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
|
||||
'book_update' => 'обновена книга',
|
||||
'book_update_notification' => 'Книгата е обновена успешно',
|
||||
'book_delete' => 'изтрита книга',
|
||||
@@ -38,6 +40,8 @@ return [
|
||||
// Bookshelves
|
||||
'bookshelf_create' => 'създаден рафт',
|
||||
'bookshelf_create_notification' => 'Рафтът е създаден успешно',
|
||||
'bookshelf_create_from_book' => 'converted book to bookshelf',
|
||||
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
|
||||
'bookshelf_update' => 'обновен рафт',
|
||||
'bookshelf_update_notification' => 'Рафтът е обновен успешно',
|
||||
'bookshelf_delete' => 'изтрит рафт',
|
||||
|
||||
@@ -38,6 +38,11 @@ return [
|
||||
'registration_email_domain_invalid' => 'Този емейл домейн към момента няма достъп до приложението',
|
||||
'register_success' => 'Благодарим Ви за регистрацията! В момента сте регистриран и сте вписани в приложението.',
|
||||
|
||||
// Login auto-initiation
|
||||
'auto_init_starting' => 'Attempting Login',
|
||||
'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.',
|
||||
'auto_init_start_link' => 'Proceed with authentication',
|
||||
|
||||
// Password Reset
|
||||
'reset_password' => 'Нулиране на паролата',
|
||||
'reset_password_send_instructions' => 'Въведете емейла си и ще ви бъде изпратен емейл с линк за нулиране на паролата.',
|
||||
|
||||
@@ -47,6 +47,8 @@ return [
|
||||
'previous' => 'Предишен',
|
||||
'filter_active' => 'Активен филтър:',
|
||||
'filter_clear' => 'Изчисти филтъра',
|
||||
'download' => 'Download',
|
||||
'open_in_tab' => 'Open in Tab',
|
||||
|
||||
// Sort Options
|
||||
'sort_options' => 'Опции за сортиране',
|
||||
|
||||
@@ -355,4 +355,16 @@ return [
|
||||
'copy_consider_images' => 'Файловете на изображенията в страницата няма да бъдат дубликирани и оригиналните изображения ще запазят връзката си със страницата, на която са били качени първоначално.',
|
||||
'copy_consider_attachments' => 'Прикачените към страницата обекти няма да бъдат копирани.',
|
||||
'copy_consider_access' => 'Смяна на местоположението, собственика или привилегиите може да направи това съдържание достъпно за тези, които не са го виждали преди.',
|
||||
|
||||
// Conversions
|
||||
'convert_to_shelf' => 'Convert to Shelf',
|
||||
'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.',
|
||||
'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.',
|
||||
'convert_book' => 'Convert Book',
|
||||
'convert_book_confirm' => 'Are you sure you want to convert this book?',
|
||||
'convert_undo_warning' => 'This cannot be as easily undone.',
|
||||
'convert_to_book' => 'Convert to Book',
|
||||
'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.',
|
||||
'convert_chapter' => 'Convert Chapter',
|
||||
'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?',
|
||||
];
|
||||
|
||||
@@ -279,6 +279,7 @@ return [
|
||||
'es_AR' => 'Español Argentina',
|
||||
'et' => 'Eesti keel',
|
||||
'eu' => 'Euskara',
|
||||
'fa' => 'فارسی',
|
||||
'fr' => 'Français',
|
||||
'he' => 'עברית',
|
||||
'hr' => 'Hrvatski',
|
||||
|
||||
@@ -28,6 +28,8 @@ return [
|
||||
// Books
|
||||
'book_create' => 'je kreirao/la knjigu',
|
||||
'book_create_notification' => 'Book successfully created',
|
||||
'book_create_from_chapter' => 'converted chapter to book',
|
||||
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
|
||||
'book_update' => 'je ažurirao/la knjigu',
|
||||
'book_update_notification' => 'Book successfully updated',
|
||||
'book_delete' => 'je izbrisao/la knjigu',
|
||||
@@ -38,6 +40,8 @@ return [
|
||||
// Bookshelves
|
||||
'bookshelf_create' => 'created bookshelf',
|
||||
'bookshelf_create_notification' => 'Bookshelf successfully created',
|
||||
'bookshelf_create_from_book' => 'converted book to bookshelf',
|
||||
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
|
||||
'bookshelf_update' => 'je ažurirao/la policu za knjige',
|
||||
'bookshelf_update_notification' => 'Bookshelf successfully updated',
|
||||
'bookshelf_delete' => 'je izbrisao/la policu za knjige',
|
||||
|
||||
@@ -38,6 +38,11 @@ return [
|
||||
'registration_email_domain_invalid' => 'Ta e-mail domena nema pristup ovoj aplikaciji',
|
||||
'register_success' => 'Hvala na registraciji! Sada ste registrovani i prijavljeni.',
|
||||
|
||||
// Login auto-initiation
|
||||
'auto_init_starting' => 'Attempting Login',
|
||||
'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.',
|
||||
'auto_init_start_link' => 'Proceed with authentication',
|
||||
|
||||
// Password Reset
|
||||
'reset_password' => 'Resetuj Lozinku',
|
||||
'reset_password_send_instructions' => 'Unesite vašu e-mail adresu ispod i na nju ćemo vam poslati e-mail sa linkom za promjenu lozinke.',
|
||||
|
||||
@@ -47,6 +47,8 @@ return [
|
||||
'previous' => 'Prethodno',
|
||||
'filter_active' => 'Active Filter:',
|
||||
'filter_clear' => 'Clear Filter',
|
||||
'download' => 'Download',
|
||||
'open_in_tab' => 'Open in Tab',
|
||||
|
||||
// Sort Options
|
||||
'sort_options' => 'Opcije sortiranja',
|
||||
|
||||
@@ -355,4 +355,16 @@ return [
|
||||
'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.',
|
||||
'copy_consider_attachments' => 'Page attachments will not be copied.',
|
||||
'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.',
|
||||
|
||||
// Conversions
|
||||
'convert_to_shelf' => 'Convert to Shelf',
|
||||
'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.',
|
||||
'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.',
|
||||
'convert_book' => 'Convert Book',
|
||||
'convert_book_confirm' => 'Are you sure you want to convert this book?',
|
||||
'convert_undo_warning' => 'This cannot be as easily undone.',
|
||||
'convert_to_book' => 'Convert to Book',
|
||||
'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.',
|
||||
'convert_chapter' => 'Convert Chapter',
|
||||
'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?',
|
||||
];
|
||||
|
||||
@@ -279,6 +279,7 @@ return [
|
||||
'es_AR' => 'Español Argentina',
|
||||
'et' => 'Eesti keel',
|
||||
'eu' => 'Euskara',
|
||||
'fa' => 'فارسی',
|
||||
'fr' => 'Français',
|
||||
'he' => 'עברית',
|
||||
'hr' => 'Hrvatski',
|
||||
|
||||
@@ -28,6 +28,8 @@ return [
|
||||
// Books
|
||||
'book_create' => 'ha creat el llibre',
|
||||
'book_create_notification' => 'Book successfully created',
|
||||
'book_create_from_chapter' => 'converted chapter to book',
|
||||
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
|
||||
'book_update' => 'ha actualitzat el llibre',
|
||||
'book_update_notification' => 'Book successfully updated',
|
||||
'book_delete' => 'ha suprimit un llibre',
|
||||
@@ -38,6 +40,8 @@ return [
|
||||
// Bookshelves
|
||||
'bookshelf_create' => 'created bookshelf',
|
||||
'bookshelf_create_notification' => 'Bookshelf successfully created',
|
||||
'bookshelf_create_from_book' => 'converted book to bookshelf',
|
||||
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
|
||||
'bookshelf_update' => 'ha actualitzat el prestatge',
|
||||
'bookshelf_update_notification' => 'Bookshelf successfully updated',
|
||||
'bookshelf_delete' => 'ha suprimit un prestatge',
|
||||
|
||||
@@ -38,6 +38,11 @@ return [
|
||||
'registration_email_domain_invalid' => 'Aquest domini de correu electrònic no té accés a aquesta aplicació',
|
||||
'register_success' => 'Gràcies per registrar-vos! Ja us hi heu registrat i heu iniciat la sessió.',
|
||||
|
||||
// Login auto-initiation
|
||||
'auto_init_starting' => 'Attempting Login',
|
||||
'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.',
|
||||
'auto_init_start_link' => 'Proceed with authentication',
|
||||
|
||||
// Password Reset
|
||||
'reset_password' => 'Restableix la contrasenya',
|
||||
'reset_password_send_instructions' => 'Introduïu la vostra adreça electrònica a continuació i us enviarem un correu electrònic amb un enllaç per a restablir la contrasenya.',
|
||||
|
||||
@@ -47,6 +47,8 @@ return [
|
||||
'previous' => 'Previous',
|
||||
'filter_active' => 'Active Filter:',
|
||||
'filter_clear' => 'Clear Filter',
|
||||
'download' => 'Download',
|
||||
'open_in_tab' => 'Open in Tab',
|
||||
|
||||
// Sort Options
|
||||
'sort_options' => 'Opcions d\'ordenació',
|
||||
|
||||
@@ -355,4 +355,16 @@ return [
|
||||
'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.',
|
||||
'copy_consider_attachments' => 'Page attachments will not be copied.',
|
||||
'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.',
|
||||
|
||||
// Conversions
|
||||
'convert_to_shelf' => 'Convert to Shelf',
|
||||
'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.',
|
||||
'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.',
|
||||
'convert_book' => 'Convert Book',
|
||||
'convert_book_confirm' => 'Are you sure you want to convert this book?',
|
||||
'convert_undo_warning' => 'This cannot be as easily undone.',
|
||||
'convert_to_book' => 'Convert to Book',
|
||||
'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.',
|
||||
'convert_chapter' => 'Convert Chapter',
|
||||
'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?',
|
||||
];
|
||||
|
||||
@@ -279,6 +279,7 @@ return [
|
||||
'es_AR' => 'Español Argentina',
|
||||
'et' => 'Eesti keel',
|
||||
'eu' => 'Euskara',
|
||||
'fa' => 'فارسی',
|
||||
'fr' => 'Français',
|
||||
'he' => 'עברית',
|
||||
'hr' => 'Hrvatski',
|
||||
|
||||
@@ -28,6 +28,8 @@ return [
|
||||
// Books
|
||||
'book_create' => 'vytvořil/a knihu',
|
||||
'book_create_notification' => 'Kniha byla úspěšně vytvořena',
|
||||
'book_create_from_chapter' => 'converted chapter to book',
|
||||
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
|
||||
'book_update' => 'aktualizoval/a knihu',
|
||||
'book_update_notification' => 'Kniha byla úspěšně aktualizována',
|
||||
'book_delete' => 'odstranil/a knihu',
|
||||
@@ -38,6 +40,8 @@ return [
|
||||
// Bookshelves
|
||||
'bookshelf_create' => 'vytvořil/a knihovnu',
|
||||
'bookshelf_create_notification' => 'Knihovna byla úspěšně vytvořena',
|
||||
'bookshelf_create_from_book' => 'converted book to bookshelf',
|
||||
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
|
||||
'bookshelf_update' => 'aktualizoval/a knihovnu',
|
||||
'bookshelf_update_notification' => 'Knihovna byla úspěšně aktualizována',
|
||||
'bookshelf_delete' => 'odstranil/a knihovnu',
|
||||
|
||||
@@ -38,6 +38,11 @@ return [
|
||||
'registration_email_domain_invalid' => 'Registrace z této e-mailové domény nejsou povoleny',
|
||||
'register_success' => 'Děkujeme za registraci! Nyní jste zaregistrováni a přihlášeni.',
|
||||
|
||||
// Login auto-initiation
|
||||
'auto_init_starting' => 'Attempting Login',
|
||||
'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.',
|
||||
'auto_init_start_link' => 'Proceed with authentication',
|
||||
|
||||
// Password Reset
|
||||
'reset_password' => 'Obnovit heslo',
|
||||
'reset_password_send_instructions' => 'Níže zadejte svou e-mailovou adresu a bude vám zaslán e-mail s odkazem na obnovení hesla.',
|
||||
|
||||
@@ -47,6 +47,8 @@ return [
|
||||
'previous' => 'Předchozí',
|
||||
'filter_active' => 'Aktivní filtr:',
|
||||
'filter_clear' => 'Zrušit filtr',
|
||||
'download' => 'Download',
|
||||
'open_in_tab' => 'Open in Tab',
|
||||
|
||||
// Sort Options
|
||||
'sort_options' => 'Možnosti řazení',
|
||||
|
||||
@@ -247,7 +247,7 @@ return [
|
||||
'pages_permissions_active' => 'Oprávnění stránky byla aktivována',
|
||||
'pages_initial_revision' => 'První vydání',
|
||||
'pages_initial_name' => 'Nová stránka',
|
||||
'pages_editing_draft_notification' => 'Právě upravujete koncept, který byl uložen před :timeDiff.',
|
||||
'pages_editing_draft_notification' => 'Právě upravujete koncept, který byl uložen :timeDiff.',
|
||||
'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' => [
|
||||
@@ -355,4 +355,16 @@ return [
|
||||
'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.',
|
||||
'copy_consider_attachments' => 'Přílohy stránky nebudou zkopírovány.',
|
||||
'copy_consider_access' => 'Po změně umístění, vlastníka nebo oprávnění může dojít k tomu, že obsah může být přístupný těm, kteří přístup dříve něměli.',
|
||||
|
||||
// Conversions
|
||||
'convert_to_shelf' => 'Convert to Shelf',
|
||||
'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.',
|
||||
'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.',
|
||||
'convert_book' => 'Convert Book',
|
||||
'convert_book_confirm' => 'Are you sure you want to convert this book?',
|
||||
'convert_undo_warning' => 'This cannot be as easily undone.',
|
||||
'convert_to_book' => 'Convert to Book',
|
||||
'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.',
|
||||
'convert_chapter' => 'Convert Chapter',
|
||||
'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?',
|
||||
];
|
||||
|
||||
@@ -279,6 +279,7 @@ return [
|
||||
'es_AR' => 'Español Argentina',
|
||||
'et' => 'Eesti keel',
|
||||
'eu' => 'Euskara',
|
||||
'fa' => 'فارسی',
|
||||
'fr' => 'Français',
|
||||
'he' => 'עברית',
|
||||
'hr' => 'Hrvatski',
|
||||
|
||||
73
resources/lang/cy/activities.php
Normal file
73
resources/lang/cy/activities.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* Activity text strings.
|
||||
* Is used for all the text within activity logs & notifications.
|
||||
*/
|
||||
return [
|
||||
|
||||
// Pages
|
||||
'page_create' => 'tudalen wedi\'i chreu',
|
||||
'page_create_notification' => 'Tudalen wedi\'i chreu\'n llwyddiannus',
|
||||
'page_update' => 'tudalen wedi\'i diweddaru',
|
||||
'page_update_notification' => 'Tudalen wedi\'i diweddaru\'n llwyddiannus',
|
||||
'page_delete' => 'tudalen wedi\'i dileu',
|
||||
'page_delete_notification' => 'Cafodd y dudalen ei dileu yn llwyddiannus',
|
||||
'page_restore' => 'tudalen wedi\'i hadfer',
|
||||
'page_restore_notification' => 'Cafodd y dudalen ei hadfer yn llwyddiannus',
|
||||
'page_move' => 'symwyd tudalen',
|
||||
|
||||
// Chapters
|
||||
'chapter_create' => 'pennod creu',
|
||||
'chapter_create_notification' => 'Pennod wedi\'i chreu\'n llwyddiannus',
|
||||
'chapter_update' => 'pennod wedi diweddaru',
|
||||
'chapter_update_notification' => 'Pennod wedi\'i diweddaru\'n llwyddiannus',
|
||||
'chapter_delete' => 'pennod wedi dileu',
|
||||
'chapter_delete_notification' => 'Pennod wedi\'i dileu\'n llwyddiannus',
|
||||
'chapter_move' => 'pennod wedi symud',
|
||||
|
||||
// Books
|
||||
'book_create' => 'llyfr wedi creu',
|
||||
'book_create_notification' => 'Llyfr wedi\'i creu\'n llwyddiannus',
|
||||
'book_create_from_chapter' => 'converted chapter to book',
|
||||
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
|
||||
'book_update' => 'llyfr wedi diweddaru',
|
||||
'book_update_notification' => 'Llyfr wedi\'i diweddaru\'n llwyddiannus',
|
||||
'book_delete' => 'llyfr wedi\'i dileu',
|
||||
'book_delete_notification' => 'Cafodd y llyfr ei dileu yn llwyddiannus',
|
||||
'book_sort' => 'llyfr wedi\'i ddidoli',
|
||||
'book_sort_notification' => 'Ail-archebwyd y llyfr yn llwyddiannus',
|
||||
|
||||
// Bookshelves
|
||||
'bookshelf_create' => 'creu silff lyfrau',
|
||||
'bookshelf_create_notification' => 'Silff lyfrau wedi\'i chreu\'n llwyddiannus',
|
||||
'bookshelf_create_from_book' => 'converted book to bookshelf',
|
||||
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
|
||||
'bookshelf_update' => 'silff lyfrau wedi\'i diweddaru',
|
||||
'bookshelf_update_notification' => 'Diweddarwyd y silff lyfrau yn llwyddiannus',
|
||||
'bookshelf_delete' => 'silff lyfrau wedi\'i dileu',
|
||||
'bookshelf_delete_notification' => 'Silff lyfrau wedi\'i dileu\'n llwyddiannus',
|
||||
|
||||
// Favourites
|
||||
'favourite_add_notification' => 'Mae ":name" wedi\'i ychwanegu at eich ffefrynnau',
|
||||
'favourite_remove_notification' => 'Mae ":name" wedi\'i tynnu o\'ch ffefrynnau',
|
||||
|
||||
// MFA
|
||||
'mfa_setup_method_notification' => 'Dull aml-ffactor wedi\'i ffurfweddu\'n llwyddiannus',
|
||||
'mfa_remove_method_notification' => 'Llwyddwyd i ddileu dull aml-ffactor',
|
||||
|
||||
// Webhooks
|
||||
'webhook_create' => 'webhook wedi creu',
|
||||
'webhook_create_notification' => 'Webhook wedi\'i creu\'n llwyddiannus',
|
||||
'webhook_update' => 'webhook wedi\'i diweddaru',
|
||||
'webhook_update_notification' => 'Webhook wedi\'i diweddaru\'n llwyddiannus',
|
||||
'webhook_delete' => 'webhook wedi\'i dileu',
|
||||
'webhook_delete_notification' => 'Webhook wedi\'i dileu\'n llwyddiannus',
|
||||
|
||||
// Users
|
||||
'user_update_notification' => 'Diweddarwyd y defnyddiwr yn llwyddiannus',
|
||||
'user_delete_notification' => 'Tynnwyd y defnyddiwr yn llwyddiannus',
|
||||
|
||||
// Other
|
||||
'commented_on' => 'gwnaeth sylwadau ar',
|
||||
'permissions_update' => 'caniatadau wedi\'u diweddaru',
|
||||
];
|
||||
115
resources/lang/cy/auth.php
Normal file
115
resources/lang/cy/auth.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* Authentication Language Lines
|
||||
* The following language lines are used during authentication for various
|
||||
* messages that we need to display to the user.
|
||||
*/
|
||||
return [
|
||||
|
||||
'failed' => 'Nid yw\'r manylion hyn yn cyfateb i\'n cofnodion.',
|
||||
'throttle' => 'Gormod o ymdrechion mewngofnodi. Rhowch gynnig arall arni o gwmpas :seconds eiliadau.',
|
||||
|
||||
// Login & Register
|
||||
'sign_up' => 'Cofrestru',
|
||||
'log_in' => 'Mewngofnodi',
|
||||
'log_in_with' => 'Mewngofnodi efo :socialDriver',
|
||||
'sign_up_with' => 'Cofrestru efo :socialDriver',
|
||||
'logout' => 'Allgofnodi',
|
||||
|
||||
'name' => 'Enw',
|
||||
'username' => 'Enw defnyddiwr',
|
||||
'email' => 'Ebost',
|
||||
'password' => 'Cyfrinair',
|
||||
'password_confirm' => 'Cadarnhau cyfrinair',
|
||||
'password_hint' => 'Rhaid bod o leiaf 8 nod',
|
||||
'forgot_password' => 'Wedi anghofio cyfrinair?',
|
||||
'remember_me' => 'Cofiwch fi',
|
||||
'ldap_email_hint' => 'Rhowch e-bost i\'w ddefnyddio ar gyfer y cyfrif hwn.',
|
||||
'create_account' => 'Creu cyfrif',
|
||||
'already_have_account' => 'Oes gennych chi gyfrif yn barod?',
|
||||
'dont_have_account' => 'Dim cyfrif?',
|
||||
'social_login' => 'Mewngofnodi cymdeithasol',
|
||||
'social_registration' => 'Cofrestru cymdeithasol',
|
||||
'social_registration_text' => 'Cofrestru a mewngofnodi gan ddefnyddio dyfais arall.',
|
||||
|
||||
'register_thanks' => 'Diolch am cofrestru!',
|
||||
'register_confirm' => 'Gwiriwch eich e-bost a chliciwch ar y botwm cadarnhau i gael mynediad i: appName.',
|
||||
'registrations_disabled' => 'Mae cofrestriadau wedi\'u hanalluogi ar hyn o bryd',
|
||||
'registration_email_domain_invalid' => 'Nid oes gan y parth e-bost hwnnw fynediad i\'r rhaglen hon',
|
||||
'register_success' => 'Diolch am arwyddo! Rydych bellach wedi cofrestru ac wedi mewngofnodi.',
|
||||
|
||||
// Login auto-initiation
|
||||
'auto_init_starting' => 'Attempting Login',
|
||||
'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.',
|
||||
'auto_init_start_link' => 'Proceed with authentication',
|
||||
|
||||
// Password Reset
|
||||
'reset_password' => 'Ailosod cyfrinair',
|
||||
'reset_password_send_instructions' => 'Rhowch eich e-bost isod ac anfonir e-bost atoch gyda dolen ailosod cyfrinair.',
|
||||
'reset_password_send_button' => 'Anfon Dolen Ailosod',
|
||||
'reset_password_sent' => 'A password reset link will be sent to :email if that email address is found in the system.',
|
||||
'reset_password_success' => 'Your password has been successfully reset.',
|
||||
'email_reset_subject' => 'Reset your :appName password',
|
||||
'email_reset_text' => 'You are receiving this email because we received a password reset request for your account.',
|
||||
'email_reset_not_requested' => 'If you did not request a password reset, no further action is required.',
|
||||
|
||||
// Email Confirmation
|
||||
'email_confirm_subject' => 'Confirm your email on :appName',
|
||||
'email_confirm_greeting' => 'Thanks for joining :appName!',
|
||||
'email_confirm_text' => 'Please confirm your email address by clicking the button below:',
|
||||
'email_confirm_action' => 'Confirm Email',
|
||||
'email_confirm_send_error' => 'Email confirmation required but the system could not send the email. Contact the admin to ensure email is set up correctly.',
|
||||
'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.',
|
||||
'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.',
|
||||
|
||||
'email_not_confirmed' => 'Email Address Not Confirmed',
|
||||
'email_not_confirmed_text' => 'Your email address has not yet been confirmed.',
|
||||
'email_not_confirmed_click_link' => 'Please click the link in the email that was sent shortly after you registered.',
|
||||
'email_not_confirmed_resend' => 'If you cannot find the email you can re-send the confirmation email by submitting the form below.',
|
||||
'email_not_confirmed_resend_button' => 'Resend Confirmation Email',
|
||||
|
||||
// User Invite
|
||||
'user_invite_email_subject' => 'You have been invited to join :appName!',
|
||||
'user_invite_email_greeting' => 'An account has been created for you on :appName.',
|
||||
'user_invite_email_text' => 'Click the button below to set an account password and gain access:',
|
||||
'user_invite_email_action' => 'Set Account Password',
|
||||
'user_invite_page_welcome' => 'Welcome to :appName!',
|
||||
'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.',
|
||||
'user_invite_page_confirm_button' => 'Confirm Password',
|
||||
'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!',
|
||||
|
||||
// Multi-factor Authentication
|
||||
'mfa_setup' => 'Setup Multi-Factor Authentication',
|
||||
'mfa_setup_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.',
|
||||
'mfa_setup_configured' => 'Already configured',
|
||||
'mfa_setup_reconfigure' => 'Reconfigure',
|
||||
'mfa_setup_remove_confirmation' => 'Are you sure you want to remove this multi-factor authentication method?',
|
||||
'mfa_setup_action' => 'Setup',
|
||||
'mfa_backup_codes_usage_limit_warning' => 'You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.',
|
||||
'mfa_option_totp_title' => 'Mobile App',
|
||||
'mfa_option_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.',
|
||||
'mfa_option_backup_codes_title' => 'Backup Codes',
|
||||
'mfa_option_backup_codes_desc' => 'Securely store a set of one-time-use backup codes which you can enter to verify your identity.',
|
||||
'mfa_gen_confirm_and_enable' => 'Confirm and Enable',
|
||||
'mfa_gen_backup_codes_title' => 'Backup Codes Setup',
|
||||
'mfa_gen_backup_codes_desc' => 'Store the below list of codes in a safe place. When accessing the system you\'ll be able to use one of the codes as a second authentication mechanism.',
|
||||
'mfa_gen_backup_codes_download' => 'Download Codes',
|
||||
'mfa_gen_backup_codes_usage_warning' => 'Each code can only be used once',
|
||||
'mfa_gen_totp_title' => 'Mobile App Setup',
|
||||
'mfa_gen_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.',
|
||||
'mfa_gen_totp_scan' => 'Scan the QR code below using your preferred authentication app to get started.',
|
||||
'mfa_gen_totp_verify_setup' => 'Verify Setup',
|
||||
'mfa_gen_totp_verify_setup_desc' => 'Verify that all is working by entering a code, generated within your authentication app, in the input box below:',
|
||||
'mfa_gen_totp_provide_code_here' => 'Provide your app generated code here',
|
||||
'mfa_verify_access' => 'Verify Access',
|
||||
'mfa_verify_access_desc' => 'Your user account requires you to confirm your identity via an additional level of verification before you\'re granted access. Verify using one of your configured methods to continue.',
|
||||
'mfa_verify_no_methods' => 'No Methods Configured',
|
||||
'mfa_verify_no_methods_desc' => 'No multi-factor authentication methods could be found for your account. You\'ll need to set up at least one method before you gain access.',
|
||||
'mfa_verify_use_totp' => 'Verify using a mobile app',
|
||||
'mfa_verify_use_backup_codes' => 'Verify using a backup code',
|
||||
'mfa_verify_backup_code' => 'Backup Code',
|
||||
'mfa_verify_backup_code_desc' => 'Enter one of your remaining backup codes below:',
|
||||
'mfa_verify_backup_code_enter_here' => 'Enter backup code here',
|
||||
'mfa_verify_totp_desc' => 'Enter the code, generated using your mobile app, below:',
|
||||
'mfa_setup_login_notification' => 'Multi-factor method configured, Please now login again using the configured method.',
|
||||
];
|
||||
104
resources/lang/cy/common.php
Normal file
104
resources/lang/cy/common.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* Common elements found throughout many areas of BookStack.
|
||||
*/
|
||||
return [
|
||||
|
||||
// Buttons
|
||||
'cancel' => 'Cancel',
|
||||
'confirm' => 'Confirm',
|
||||
'back' => 'Back',
|
||||
'save' => 'Save',
|
||||
'continue' => 'Continue',
|
||||
'select' => 'Select',
|
||||
'toggle_all' => 'Toggle All',
|
||||
'more' => 'More',
|
||||
|
||||
// Form Labels
|
||||
'name' => 'Name',
|
||||
'description' => 'Description',
|
||||
'role' => 'Role',
|
||||
'cover_image' => 'Cover image',
|
||||
'cover_image_description' => 'This image should be approx 440x250px.',
|
||||
|
||||
// Actions
|
||||
'actions' => 'Actions',
|
||||
'view' => 'View',
|
||||
'view_all' => 'View All',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'edit' => 'Edit',
|
||||
'sort' => 'Sort',
|
||||
'move' => 'Move',
|
||||
'copy' => 'Copy',
|
||||
'reply' => 'Reply',
|
||||
'delete' => 'Delete',
|
||||
'delete_confirm' => 'Confirm Deletion',
|
||||
'search' => 'Search',
|
||||
'search_clear' => 'Clear Search',
|
||||
'reset' => 'Reset',
|
||||
'remove' => 'Remove',
|
||||
'add' => 'Add',
|
||||
'configure' => 'Configure',
|
||||
'fullscreen' => 'Fullscreen',
|
||||
'favourite' => 'Favourite',
|
||||
'unfavourite' => 'Unfavourite',
|
||||
'next' => 'Next',
|
||||
'previous' => 'Previous',
|
||||
'filter_active' => 'Active Filter:',
|
||||
'filter_clear' => 'Clear Filter',
|
||||
'download' => 'Download',
|
||||
'open_in_tab' => 'Open in Tab',
|
||||
|
||||
// Sort Options
|
||||
'sort_options' => 'Sort Options',
|
||||
'sort_direction_toggle' => 'Sort Direction Toggle',
|
||||
'sort_ascending' => 'Sort Ascending',
|
||||
'sort_descending' => 'Sort Descending',
|
||||
'sort_name' => 'Name',
|
||||
'sort_default' => 'Default',
|
||||
'sort_created_at' => 'Created Date',
|
||||
'sort_updated_at' => 'Updated Date',
|
||||
|
||||
// Misc
|
||||
'deleted_user' => 'Deleted User',
|
||||
'no_activity' => 'No activity to show',
|
||||
'no_items' => 'No items available',
|
||||
'back_to_top' => 'Back to top',
|
||||
'skip_to_main_content' => 'Skip to main content',
|
||||
'toggle_details' => 'Toggle Details',
|
||||
'toggle_thumbnails' => 'Toggle Thumbnails',
|
||||
'details' => 'Details',
|
||||
'grid_view' => 'Grid View',
|
||||
'list_view' => 'List View',
|
||||
'default' => 'Default',
|
||||
'breadcrumb' => 'Breadcrumb',
|
||||
'status' => 'Status',
|
||||
'status_active' => 'Active',
|
||||
'status_inactive' => 'Inactive',
|
||||
'never' => 'Never',
|
||||
'none' => 'None',
|
||||
|
||||
// Header
|
||||
'header_menu_expand' => 'Expand Header Menu',
|
||||
'profile_menu' => 'Profile Menu',
|
||||
'view_profile' => 'View Profile',
|
||||
'edit_profile' => 'Edit Profile',
|
||||
'dark_mode' => 'Dark Mode',
|
||||
'light_mode' => 'Light Mode',
|
||||
|
||||
// Layout tabs
|
||||
'tab_info' => 'Info',
|
||||
'tab_info_label' => 'Tab: Show Secondary Information',
|
||||
'tab_content' => 'Content',
|
||||
'tab_content_label' => 'Tab: Show Primary Content',
|
||||
|
||||
// Email Content
|
||||
'email_action_help' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:',
|
||||
'email_rights' => 'All rights reserved',
|
||||
|
||||
// Footer Link Options
|
||||
// Not directly used but available for convenience to users.
|
||||
'privacy_policy' => 'Privacy Policy',
|
||||
'terms_of_service' => 'Terms of Service',
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user