Compare commits

...

93 Commits

Author SHA1 Message Date
Dan Brown
5ee79d16c9 Updated version and assets for release v22.06.2 2022-06-28 11:57:37 +01:00
Dan Brown
a1ea4006e0 Merge branch 'development' into release 2022-06-28 11:57:24 +01:00
Dan Brown
a721405202 New Crowdin updates (#3540) 2022-06-28 11:56:07 +01:00
Dan Brown
d20aacb732 Merge branch '3535-group-sync-fix' into development 2022-06-28 11:47:22 +01:00
Dan Brown
65fa96e405 New Crowdin updates (#3531) 2022-06-27 14:29:10 +01:00
Dan Brown
736d6afb7d Aligned entity-selector-popup button and dblclick behaviour
Fixes #3534
2022-06-27 14:27:29 +01:00
Dan Brown
0bcd1795cb Auth group sync: Fixed unintential mapping behaviour change
Due to change in how casing was handled when used in the "External Auth
ID" role field.
Likely related to #3535.
Added test to cover.
2022-06-27 14:18:46 +01:00
Dan Brown
47887ec595 Added path example to visual theme system 2022-06-27 13:38:51 +01:00
Dan Brown
9078188939 Updated version and assets for release v22.06.1 2022-06-25 14:33:07 +01:00
Dan Brown
ed0aad1a7a Merge branch 'development' into release 2022-06-25 14:32:49 +01:00
Dan Brown
43749cd94e Merge branch 'development' of github.com:BookStackApp/BookStack into development 2022-06-25 14:27:46 +01:00
Dan Brown
107df6c28f Applied StyleCI changes 2022-06-25 14:27:32 +01:00
Dan Brown
c1d1ec5b89 New Crowdin updates (#3526) 2022-06-25 14:26:40 +01:00
Dan Brown
12c282597d Fixed non-translated category strings
For #3529
2022-06-25 14:24:38 +01:00
Dan Brown
c9d0e22132 Updated entity-selector-popup to reset on selection
Better links the core selector component to the popup version, with new
public methods for direct controlling.

For #3528
2022-06-25 14:13:17 +01:00
Dan Brown
0801955a26 Fixed grid layouts being pushed out by child content
Ran a quick app run-thorugh in FireFox & Chrome, No secondary affects
immediately noticed but possible this could cause changes elsewhere due
to wide-spread grid item child targeting.

For #3523
2022-06-25 13:55:57 +01:00
Dan Brown
3ed1ffdbeb Fixed issue blocking tags on book update
For #3527
2022-06-25 13:46:55 +01:00
Dan Brown
5c59cfb020 Updated version and assets for release v22.06 2022-06-24 11:50:56 +01:00
Dan Brown
3ca15ad68a Merge branch 'development' into release 2022-06-24 11:45:29 +01:00
Dan Brown
36f0a68f1b Added missing welsh locale to config 2022-06-24 11:42:38 +01:00
Dan Brown
ed981cbab1 New Crowdin updates (#3428) 2022-06-24 11:35:59 +01:00
Dan Brown
f69af8933c Updated translator list before v22.06 release 2022-06-24 11:30:15 +01:00
Dan Brown
46d71a181e Updated php deps and applied styleci changes 2022-06-22 12:49:58 +01:00
Dan Brown
8d8da31fdd Added base template convenience partials for theme system users
Included test to cover usage and paths.
Closes #894
2022-06-22 12:47:31 +01:00
Dan Brown
0d9b5a9d90 Merge branch 'login-auto-redirect' into development 2022-06-21 15:38:01 +01:00
Dan Brown
8b211ed461 Review and update of login auto initiation PR
For PR #3406

- Updated naming from 'redirect' to 'initate/initation'.
- Updated phpunit.xml and .env.example.complete files with the new
  option.
- Cleaned up controller logic a bit.
- Added content and design to the new initation view to not leave user
  on a blank view for a while.
- Added non-JS button to initiation view as fallback option for
  progression.
- Moved new test to it's own Test class and expanded with additional
  scenario tests for better functionality coverage.
2022-06-21 15:32:18 +01:00
Dan Brown
9dd69b04b8 Fixed code snippets being added as single line
TinyMCE was adding attributes to <br> elements within code blocks which
would then not be converted to newlines by our code regex match.
This changes the conversion to use dom querying instead.

Fixes #3507
2022-06-21 12:01:06 +01:00
Dan Brown
0c6f598d91 Fixed issue where text after line breaks not indexed
Linebreaks would previously essentially be removed during index and
hence joined to adjacent words, breaking prefix matching.
Added test to cover.
For #3508
2022-06-20 23:47:42 +01:00
Dan Brown
df94b73e29 Merge pull request #3512 from BookStackApp/code_manager_updates
WYSIWYG Code Editor Updates
2022-06-20 23:13:28 +01:00
Dan Brown
7d4b941abf Added code editor changes mobile design handling 2022-06-20 23:12:07 +01:00
Dan Brown
d181106df3 Adjusted code manager changes for dark mode 2022-06-20 23:06:54 +01:00
Dan Brown
75110813e6 Aligned other popup windows
Primary change was altering image-manager to use same footer bar style
as other windows.
2022-06-20 23:02:06 +01:00
Dan Brown
1e41546e51 Updated code editor language lists
To align and update supported languages.

Related to #3511 and #3494
2022-06-20 17:49:56 +01:00
Dan Brown
f39b565a1c Tweaked code editor sidebar side to be smaller 2022-06-20 17:16:28 +01:00
Dan Brown
77cd550fae Polished up code editor design 2022-06-20 17:11:34 +01:00
Dan Brown
96d9077479 Started design changes to the code-editor 2022-06-20 13:42:12 +01:00
Dan Brown
be1d691529 Merge pull request #3499 from BookStackApp/convert_hierachy
Chapter and Book Conversion Actions
2022-06-20 12:51:13 +01:00
Dan Brown
8cde362f6f Removed bad trailing comma in method 2022-06-19 18:45:48 +01:00
Dan Brown
388343aeb0 Fixed failing tests after conversion changes 2022-06-19 18:44:34 +01:00
Dan Brown
ba25dda031 Applied styleci changes for conversion work 2022-06-19 18:14:53 +01:00
Dan Brown
85f59b5275 Added tests for content conversion action permissions
- Updated 'removePermissionFromUser' test helper to work for
  entity-permissions that become part of the joint permissions system.
2022-06-19 18:12:36 +01:00
Dan Brown
65d4505079 Added tests and doc updates for shelf/book cover image API abilities 2022-06-19 17:26:23 +01:00
Dan Brown
663f81a2b1 Added tests to cover convert functionality
Also updated cloner class with typed properties.
2022-06-19 16:57:33 +01:00
Dan Brown
f145ffc930 Extracted conversion text to translation file 2022-06-19 16:23:18 +01:00
Dan Brown
19d7e26dda Merge pull request #3503 from andrii-bodnar/fix/crowdin-name
Fix Crowdin name in the language_request issue template
2022-06-16 12:07:40 +01:00
Andrii Bodnar
a13b9d8d14 Fix Crowdin name in the language_request issue template 2022-06-16 11:34:27 +03:00
Dan Brown
8c67011a1d Got book to shelf conversions working
- Also extracted shelf to book view elements to own partial.
- Fixed some existing logic including image param handling in update
  request and activity logging against correct element.
2022-06-15 15:05:08 +01:00
Dan Brown
8da856bac3 Got chapter conversion to books working
- Added required UI within edit view.
- Added required routes and controller actions.
2022-06-14 16:42:29 +01:00
Dan Brown
90ec40691a Added clone of entity permissions on chapter/book promotion 2022-06-14 15:55:44 +01:00
Dan Brown
d676e1e824 Started work on hierachy conversion actions
- Updates book/shelf cover image handling for easier cloning/handling.
- Adds core logic for promoting books/chapters up a level.
- Enables usage of book/shelf cover image via API.

Related to #1087
2022-06-13 17:20:21 +01:00
Dan Brown
0a05119aa5 Applied styleci changes, updated composer deps 2022-06-10 12:37:14 +01:00
Dan Brown
abc283fc64 Extracted download response logic to its own class
Cleans up base controller and groups up download & streaming logic for
potential future easier addition of range request support.
2022-06-08 23:50:42 +01:00
Dan Brown
e72ade727d Added audio mimes to our safe list for inline serving
Closes #3485
2022-06-08 22:30:55 +01:00
Dan Brown
c8b123bfac Updated composer deps, applied styleci changes 2022-06-08 18:00:30 +01:00
Dan Brown
88012449f3 Reorganised and split out export templates & styles
Moved export templates elements into their own folder for better
grouping of logical usage.
Within the base export template, added some body classes to allow easier
targeted customisation via custom head css.
Split content of export templates into smaller partials for easier
future customization.

Closes #3443
2022-06-08 17:56:59 +01:00
Dan Brown
e00d88f45d Updated markdown preview to update on diff-basis
Uses vdom system to diff and update the current markdown preview view
instead of requiring a full HTML replace change.
This should provide better performance, expecially where dynamically
loaded content such as iframes were in use.

Closes #3454
2022-06-07 16:07:28 +01:00
Dan Brown
3fe666f36a Updated image drop handling to respect original file name
Now uses the previously timestamp gen name as a backup to the original
name. Aligns with the image manager upload which uses the original name
where given.

Closes #3470
2022-06-07 14:59:00 +01:00
Dan Brown
3f271ebecb Removed image_id property from books & shelves api docs
This was either not provided or not provided for the last 18 months.
Likely not providing much value as-is so removing.

Closes #3474
2022-06-07 14:30:43 +01:00
Dan Brown
7c597a05f6 Added codeblock latex/stext support
For #3458
2022-05-30 18:41:40 +01:00
Dan Brown
16e023985d Prevented inadvertant logging during MFA flow
- Added StoppedAuthenticationException to dontReport list.
- Added test to cover.

Closes #3468
2022-05-30 18:31:08 +01:00
Dan Brown
43cbab2822 Merge branch 'development' of github.com:BookStackApp/BookStack into development 2022-05-30 17:01:46 +01:00
Dan Brown
1a3505c899 Updated JS deps 2022-05-30 17:01:32 +01:00
Dan Brown
2930025f51 Update dev version to track current release target 2022-05-30 16:58:01 +01:00
Dan Brown
39fcf3a68f Merge pull request #3416 from BookStackApp/group_sync_comma_escaping
Added ability to escape role "External Auth ID" commas
2022-05-30 16:55:32 +01:00
Dan Brown
6ce34fe6cc Merge pull request #3433 from BookStackApp/tiny_improvements
Bunch of tiny improvements
2022-05-30 16:51:59 +01:00
Dan Brown
3c3aed58aa Updated funding with kofi link 2022-05-30 16:49:24 +01:00
Dan Brown
73f36b279e Updated PHP deps 2022-05-30 16:46:48 +01:00
Dan Brown
2b817e7d24 Updated attachment links to have dropdown for open type
- Allows easier accessibility of inline attachments.
- Introduces a new split-icon-list-item thingy to support such cases
  where only part of the button is actually linked.
2022-05-19 17:38:04 +01:00
Dan Brown
cb10ad804f Made chapter toggle in book sidebar nav more consistent
- Now has a hover state to match other items.
- Now spans the full sidebar with like other items.
- Also updated chapter-toggle to a chapter-contents component, following
  the newer component system.
2022-05-18 14:06:40 +01:00
Dan Brown
eeccc2ef10 Readjusted book child item styles after other changes
Was extra space showing due to structure changes and flex gap.
2022-05-18 13:28:34 +01:00
Dan Brown
b030c1398b Tweaked chapter list item styles
- Improves animation smoothness
- Changed animation slideup/down animations to use max-height instead of height
  to better avoid jutter at the end.
- Cleaned spacing to match page items in books listing.
2022-05-18 13:18:21 +01:00
Dan Brown
4759fa1e1f Made the "Custom HTML Head Content" setting a highlighted code editor 2022-05-17 17:39:31 +01:00
Dan Brown
cb1c2db282 Aligned collapsed header dropdown item styles
Previously the desktop-visible items would style different when collapsed
into the expanded dropdown menu, compared to existing items.
2022-05-17 14:27:58 +01:00
Dan Brown
4866a3a198 Refined header bar styles
- Updated many items to be flexbox-based.
- Updated & aligned hover states across header bar items.
2022-05-17 14:16:43 +01:00
Dan Brown
340c9ec7a1 Fixed some inputs affected by height changes 2022-05-17 13:37:43 +01:00
Dan Brown
49498cfaf9 Fixed entity-specific tag counts listing
Was reporting wrong due to use of old polymorphic namespace references.
Test was not picking up as assertElementContains had wider scope than
expected, looking within the HTML of the element instead of the text
which you might expect. Updated test helper to look at text instead.
2022-05-16 14:05:21 +01:00
Dan Brown
3a4aa81115 Removed dialog debug script from default home
Accidentally left in from before.
Closes #3430
2022-05-16 13:36:42 +01:00
Dan Brown
d20c74babf Improved input size consistency
Specifically updates dropdown search and user-search implementation,
although does affect all inputs.
Decouples breadcrum and select-style dropdown search toggles.

Addresses #2678
2022-05-14 16:05:29 +01:00
Dan Brown
9fda0df798 Updated dropdown search boxe positions to align with other dropdowns 2022-05-14 14:19:54 +01:00
Dan Brown
6fa699a835 Fixed skip-to-content link shadow being slightly visible
Would cause a slight dark area in top left of view while hidden.
2022-05-14 13:59:10 +01:00
Dan Brown
78920d7d65 Updated tri-layout sidebars to not be cut-off by padding
Would cause effect where scroll area would be cut of by spacing which
looked a bit strange. This retains the same padding sizes but cuts the
content at the header or top of viewport.
2022-05-14 13:55:03 +01:00
Dan Brown
35a47a273b Added animation transition for breadcrumb dropdown load
Animates the height on breadcrumb dropdown menus to transition to the
loaded animations quicker. Includes a new animation helper for doing
similar tasks in future.
2022-05-14 13:32:25 +01:00
Dan Brown
89dfa43e73 Fixed loading animation delay
Loading animation would show in an unready state due to animation-delay
on components. Updated to a negative delay to ensure elements were in
correct positions right away upon show.
2022-05-14 13:31:24 +01:00
Dan Brown
2c74dfd1d4 Updated breadcrumb dropdown styles, improved keyboard nav
- Removed harsh theme color border between search and content.
- Prevented intermediate focus on list container to align arrow & tab
  behaviour, and to get to content quicker.
2022-05-14 13:11:48 +01:00
Dan Brown
e6864a9cff Improved card list design
- Removed border and rounded list item styles to make hover states have
  less edge detail and to align with other UI elements.
- In expanded-detail view, removed space used for entity description if
  there is not description content existing.
2022-05-14 12:54:23 +01:00
Dan Brown
60e319c4b4 Tidied up book navigation styles
- Removed background track line since it would darken entity item bars.
- Updated item spacing to be a bit tighter.
- Updated action hover styles to be a bit lighter, and visible on dark
  mode, to fit rest of system.
2022-05-13 18:34:47 +01:00
Dan Brown
24b31b624c Cleaned up entity details listing 2022-05-13 18:03:43 +01:00
Dan Brown
a0fe6147d8 Improved the display of dropdown menus
- Tweaked styling to add a little extra shadow and be more rounded to
  match other UI areas.
- Added slight horizontal inset when in right sidebar to prevent shadow
  being cut-off in most cases.
- Added logic to "drop upwards" if dropping down would take the menu
  offscreen.
2022-05-13 17:12:45 +01:00
Dan Brown
221d910ff2 Reduced excess margin in chapter contents lists 2022-05-12 17:27:57 +01:00
Dan Brown
bef2045df1 Embedded css sources for easier firefox dev work 2022-05-12 17:27:29 +01:00
Dan Brown
f021823287 Updated default value for secure session detection
Updated default value for APP_URL so that the startsWith call is not
passed null, since that causes deprecation notice in PHP8.1.
Would show when APP_URL was not set, adding extra confusiion.
2022-05-11 16:47:09 +01:00
Dan Brown
d795af04df Added ability to escape role "External Auth ID" commas
- Using a backslash in this field before a comma.
- Could potentially (Although unlikely) be a breaking change.

For #3405
2022-05-04 21:03:13 +01:00
Robert Meredith
d5ce6b680c Skip intermediate login page with single provider 2022-05-02 20:35:11 +10:00
325 changed files with 5677 additions and 2056 deletions

View File

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

@@ -1,3 +1,4 @@
# These are supported funding model platforms
github: [ssddanbrown]
ko_fi: ssddanbrown

View File

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

View File

@@ -243,3 +243,18 @@ 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

View File

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

View File

@@ -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');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.
*/

View File

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

View File

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

View File

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

View 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;
}
}

View File

@@ -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'));

View File

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

View File

@@ -21,6 +21,7 @@ class Handler extends ExceptionHandler
*/
protected $dontReport = [
NotFoundException::class,
StoppedAuthenticationException::class,
];
/**

View File

@@ -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()),
],
];
}
}

View File

@@ -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');
}
}

View File

@@ -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()),
],
];
}
}

View File

@@ -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');
}
}

View File

@@ -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');
}
}

View File

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

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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');
}
}

View File

@@ -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());
}

View File

@@ -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());
}
}

View File

@@ -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');
}
}

View File

@@ -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,74 +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, if we're in one, otherwise we still use memory.
// Output buffer may or may not exist depending on PHP `output_buffering` setting.
// Ignore in testing since output buffers are used to gather a response.
if (!empty(ob_get_status()) && !app()->runningUnitTests()) {
ob_end_clean();
}
fpassthru($stream);
fclose($stream);
}, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
'X-Content-Type-Options' => 'nosniff',
]);
}
/**
* 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());
}
/**

View File

@@ -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');
}
}

View 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',
];
}
}

View File

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

View File

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

587
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -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' => '',
];
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

47
public/dist/code.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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

View File

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

View 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;

View File

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

View File

@@ -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();

View 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;

View File

@@ -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);
}

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

@@ -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');

View File

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

View 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));
}

View File

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

View File

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

View File

@@ -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' => 'تم تحديث الرف',

View File

@@ -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' => 'أدخل بريدك الإلكتروني بالأسفل وسيتم إرسال رسالة برابط لاستعادة كلمة المرور.',

View File

@@ -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' => 'خيارات الفرز',

View File

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

View File

@@ -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' => 'изтрит рафт',

View File

@@ -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' => 'Въведете емейла си и ще ви бъде изпратен емейл с линк за нулиране на паролата.',

View File

@@ -47,6 +47,8 @@ return [
'previous' => 'Предишен',
'filter_active' => 'Активен филтър:',
'filter_clear' => 'Изчисти филтъра',
'download' => 'Download',
'open_in_tab' => 'Open in Tab',
// Sort Options
'sort_options' => 'Опции за сортиране',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
View 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.',
];

View 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 youre 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',
];

View File

@@ -0,0 +1,34 @@
<?php
/**
* Text used in custom JavaScript driven components.
*/
return [
// Image Manager
'image_select' => 'Image Select',
'image_all' => 'All',
'image_all_title' => 'View all images',
'image_book_title' => 'View images uploaded to this book',
'image_page_title' => 'View images uploaded to this page',
'image_search_hint' => 'Search by image name',
'image_uploaded' => 'Uploaded :uploadedDate',
'image_load_more' => 'Load More',
'image_image_name' => 'Image Name',
'image_delete_used' => 'This image is used in the pages below.',
'image_delete_confirm_text' => 'Are you sure you want to delete this image?',
'image_select_image' => 'Select Image',
'image_dropzone' => 'Drop images or click here to upload',
'images_deleted' => 'Images Deleted',
'image_preview' => 'Image Preview',
'image_upload_success' => 'Image uploaded successfully',
'image_update_success' => 'Image details successfully updated',
'image_delete_success' => 'Image successfully deleted',
'image_upload_remove' => 'Remove',
// Code Editor
'code_editor' => 'Edit Code',
'code_language' => 'Code Language',
'code_content' => 'Code Content',
'code_session_history' => 'Session History',
'code_save' => 'Save Code',
];

View File

@@ -0,0 +1,171 @@
<?php
/**
* Page Editor Lines
* Contains text strings used within the user interface of the
* WYSIWYG page editor. Some Markdown editor strings may still
* exist in the 'entities' file instead since this was added later.
*/
return [
// General editor terms
'general' => 'General',
'advanced' => 'Advanced',
'none' => 'None',
'cancel' => 'Cancel',
'save' => 'Save',
'close' => 'Close',
'undo' => 'Undo',
'redo' => 'Redo',
'left' => 'Left',
'center' => 'Center',
'right' => 'Right',
'top' => 'Top',
'middle' => 'Middle',
'bottom' => 'Bottom',
'width' => 'Width',
'height' => 'Height',
'More' => 'More',
'select' => 'Select...',
// Toolbar
'formats' => 'Formats',
'header_large' => 'Large Header',
'header_medium' => 'Medium Header',
'header_small' => 'Small Header',
'header_tiny' => 'Tiny Header',
'paragraph' => 'Paragraph',
'blockquote' => 'Blockquote',
'inline_code' => 'Inline code',
'callouts' => 'Callouts',
'callout_information' => 'Information',
'callout_success' => 'Success',
'callout_warning' => 'Warning',
'callout_danger' => 'Danger',
'bold' => 'Bold',
'italic' => 'Italic',
'underline' => 'Underline',
'strikethrough' => 'Strikethrough',
'superscript' => 'Superscript',
'subscript' => 'Subscript',
'text_color' => 'Text color',
'custom_color' => 'Custom color',
'remove_color' => 'Remove color',
'background_color' => 'Background color',
'align_left' => 'Align left',
'align_center' => 'Align center',
'align_right' => 'Align right',
'align_justify' => 'Justify',
'list_bullet' => 'Bullet list',
'list_numbered' => 'Numbered list',
'list_task' => 'Task list',
'indent_increase' => 'Increase indent',
'indent_decrease' => 'Decrease indent',
'table' => 'Table',
'insert_image' => 'Insert image',
'insert_image_title' => 'Insert/Edit Image',
'insert_link' => 'Insert/edit link',
'insert_link_title' => 'Insert/Edit Link',
'insert_horizontal_line' => 'Insert horizontal line',
'insert_code_block' => 'Insert code block',
'insert_drawing' => 'Insert/edit drawing',
'drawing_manager' => 'Drawing manager',
'insert_media' => 'Insert/edit media',
'insert_media_title' => 'Insert/Edit Media',
'clear_formatting' => 'Clear formatting',
'source_code' => 'Source code',
'source_code_title' => 'Source Code',
'fullscreen' => 'Fullscreen',
'image_options' => 'Image options',
// Tables
'table_properties' => 'Table properties',
'table_properties_title' => 'Table Properties',
'delete_table' => 'Delete table',
'insert_row_before' => 'Insert row before',
'insert_row_after' => 'Insert row after',
'delete_row' => 'Delete row',
'insert_column_before' => 'Insert column before',
'insert_column_after' => 'Insert column after',
'delete_column' => 'Delete column',
'table_cell' => 'Cell',
'table_row' => 'Row',
'table_column' => 'Column',
'cell_properties' => 'Cell properties',
'cell_properties_title' => 'Cell Properties',
'cell_type' => 'Cell type',
'cell_type_cell' => 'Cell',
'cell_scope' => 'Scope',
'cell_type_header' => 'Header cell',
'merge_cells' => 'Merge cells',
'split_cell' => 'Split cell',
'table_row_group' => 'Row Group',
'table_column_group' => 'Column Group',
'horizontal_align' => 'Horizontal align',
'vertical_align' => 'Vertical align',
'border_width' => 'Border width',
'border_style' => 'Border style',
'border_color' => 'Border color',
'row_properties' => 'Row properties',
'row_properties_title' => 'Row Properties',
'cut_row' => 'Cut row',
'copy_row' => 'Copy row',
'paste_row_before' => 'Paste row before',
'paste_row_after' => 'Paste row after',
'row_type' => 'Row type',
'row_type_header' => 'Header',
'row_type_body' => 'Body',
'row_type_footer' => 'Footer',
'alignment' => 'Alignment',
'cut_column' => 'Cut column',
'copy_column' => 'Copy column',
'paste_column_before' => 'Paste column before',
'paste_column_after' => 'Paste column after',
'cell_padding' => 'Cell padding',
'cell_spacing' => 'Cell spacing',
'caption' => 'Caption',
'show_caption' => 'Show caption',
'constrain' => 'Constrain proportions',
'cell_border_solid' => 'Solid',
'cell_border_dotted' => 'Dotted',
'cell_border_dashed' => 'Dashed',
'cell_border_double' => 'Double',
'cell_border_groove' => 'Groove',
'cell_border_ridge' => 'Ridge',
'cell_border_inset' => 'Inset',
'cell_border_outset' => 'Outset',
'cell_border_none' => 'None',
'cell_border_hidden' => 'Hidden',
// Images, links, details/summary & embed
'source' => 'Source',
'alt_desc' => 'Alternative description',
'embed' => 'Embed',
'paste_embed' => 'Paste your embed code below:',
'url' => 'URL',
'text_to_display' => 'Text to display',
'title' => 'Title',
'open_link' => 'Open link in...',
'open_link_current' => 'Current window',
'open_link_new' => 'New window',
'insert_collapsible' => 'Insert collapsible block',
'collapsible_unwrap' => 'Unwrap',
'edit_label' => 'Edit label',
'toggle_open_closed' => 'Toggle open/closed',
'collapsible_edit' => 'Edit collapsible block',
'toggle_label' => 'Toggle label',
// About view
'about' => 'About the editor',
'about_title' => 'About the WYSIWYG Editor',
'editor_license' => 'Editor License & Copyright',
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.',
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
'save_continue' => 'Save Page & Continue',
'callouts_cycle' => '(Keep pressing to toggle through types)',
'link_selector' => 'Link to content',
'shortcuts' => 'Shortcuts',
'shortcut' => 'Shortcut',
'shortcuts_intro' => 'The following shortcuts are available in the editor:',
'windows_linux' => '(Windows/Linux)',
'mac' => '(Mac)',
'description' => 'Description',
];

View File

@@ -0,0 +1,370 @@
<?php
/**
* Text used for 'Entities' (Document Structure Elements) such as
* Books, Shelves, Chapters & Pages
*/
return [
// Shared
'recently_created' => 'Recently Created',
'recently_created_pages' => 'Recently Created Pages',
'recently_updated_pages' => 'Recently Updated Pages',
'recently_created_chapters' => 'Recently Created Chapters',
'recently_created_books' => 'Recently Created Books',
'recently_created_shelves' => 'Recently Created Shelves',
'recently_update' => 'Recently Updated',
'recently_viewed' => 'Recently Viewed',
'recent_activity' => 'Recent Activity',
'create_now' => 'Create one now',
'revisions' => 'Revisions',
'meta_revision' => 'Revision #:revisionCount',
'meta_created' => 'Created :timeLength',
'meta_created_name' => 'Created :timeLength by :user',
'meta_updated' => 'Updated :timeLength',
'meta_updated_name' => 'Updated :timeLength by :user',
'meta_owned_name' => 'Owned by :user',
'entity_select' => 'Entity Select',
'images' => 'Images',
'my_recent_drafts' => 'My Recent Drafts',
'my_recently_viewed' => 'My Recently Viewed',
'my_most_viewed_favourites' => 'My Most Viewed Favourites',
'my_favourites' => 'My Favourites',
'no_pages_viewed' => 'You have not viewed any pages',
'no_pages_recently_created' => 'No pages have been recently created',
'no_pages_recently_updated' => 'No pages have been recently updated',
'export' => 'Export',
'export_html' => 'Contained Web File',
'export_pdf' => 'PDF File',
'export_text' => 'Plain Text File',
'export_md' => 'Markdown File',
// Permissions and restrictions
'permissions' => 'Permissions',
'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.',
'permissions_enable' => 'Enable Custom Permissions',
'permissions_save' => 'Save Permissions',
'permissions_owner' => 'Owner',
// Search
'search_results' => 'Search Results',
'search_total_results_found' => ':count result found|:count total results found',
'search_clear' => 'Clear Search',
'search_no_pages' => 'No pages matched this search',
'search_for_term' => 'Search for :term',
'search_more' => 'More Results',
'search_advanced' => 'Advanced Search',
'search_terms' => 'Search Terms',
'search_content_type' => 'Content Type',
'search_exact_matches' => 'Exact Matches',
'search_tags' => 'Tag Searches',
'search_options' => 'Options',
'search_viewed_by_me' => 'Viewed by me',
'search_not_viewed_by_me' => 'Not viewed by me',
'search_permissions_set' => 'Permissions set',
'search_created_by_me' => 'Created by me',
'search_updated_by_me' => 'Updated by me',
'search_owned_by_me' => 'Owned by me',
'search_date_options' => 'Date Options',
'search_updated_before' => 'Updated before',
'search_updated_after' => 'Updated after',
'search_created_before' => 'Created before',
'search_created_after' => 'Created after',
'search_set_date' => 'Set Date',
'search_update' => 'Update Search',
// Shelves
'shelf' => 'Shelf',
'shelves' => 'Shelves',
'x_shelves' => ':count Shelf|:count Shelves',
'shelves_long' => 'Bookshelves',
'shelves_empty' => 'No shelves have been created',
'shelves_create' => 'Create New Shelf',
'shelves_popular' => 'Popular Shelves',
'shelves_new' => 'New Shelves',
'shelves_new_action' => 'New Shelf',
'shelves_popular_empty' => 'The most popular shelves will appear here.',
'shelves_new_empty' => 'The most recently created shelves will appear here.',
'shelves_save' => 'Save Shelf',
'shelves_books' => 'Books on this shelf',
'shelves_add_books' => 'Add books to this shelf',
'shelves_drag_books' => 'Drag books here to add them to this shelf',
'shelves_empty_contents' => 'This shelf has no books assigned to it',
'shelves_edit_and_assign' => 'Edit shelf to assign books',
'shelves_edit_named' => 'Edit Bookshelf :name',
'shelves_edit' => 'Edit Bookshelf',
'shelves_delete' => 'Delete Bookshelf',
'shelves_delete_named' => 'Delete Bookshelf :name',
'shelves_delete_explain' => "This will delete the bookshelf with the name ':name'. Contained books will not be deleted.",
'shelves_delete_confirmation' => 'Are you sure you want to delete this bookshelf?',
'shelves_permissions' => 'Bookshelf Permissions',
'shelves_permissions_updated' => 'Bookshelf Permissions Updated',
'shelves_permissions_active' => 'Bookshelf Permissions Active',
'shelves_permissions_cascade_warning' => 'Permissions on bookshelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.',
'shelves_copy_permissions_to_books' => 'Copy Permissions to Books',
'shelves_copy_permissions' => 'Copy Permissions',
'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
'shelves_copy_permission_success' => 'Bookshelf permissions copied to :count books',
// Books
'book' => 'Book',
'books' => 'Books',
'x_books' => ':count Book|:count Books',
'books_empty' => 'No books have been created',
'books_popular' => 'Popular Books',
'books_recent' => 'Recent Books',
'books_new' => 'New Books',
'books_new_action' => 'New Book',
'books_popular_empty' => 'The most popular books will appear here.',
'books_new_empty' => 'The most recently created books will appear here.',
'books_create' => 'Create New Book',
'books_delete' => 'Delete Book',
'books_delete_named' => 'Delete Book :bookName',
'books_delete_explain' => 'This will delete the book with the name \':bookName\'. All pages and chapters will be removed.',
'books_delete_confirmation' => 'Are you sure you want to delete this book?',
'books_edit' => 'Edit Book',
'books_edit_named' => 'Edit Book :bookName',
'books_form_book_name' => 'Book Name',
'books_save' => 'Save Book',
'books_permissions' => 'Book Permissions',
'books_permissions_updated' => 'Book Permissions Updated',
'books_empty_contents' => 'No pages or chapters have been created for this book.',
'books_empty_create_page' => 'Create a new page',
'books_empty_sort_current_book' => 'Sort the current book',
'books_empty_add_chapter' => 'Add a chapter',
'books_permissions_active' => 'Book Permissions Active',
'books_search_this' => 'Search this book',
'books_navigation' => 'Book Navigation',
'books_sort' => 'Sort Book Contents',
'books_sort_named' => 'Sort Book :bookName',
'books_sort_name' => 'Sort by Name',
'books_sort_created' => 'Sort by Created Date',
'books_sort_updated' => 'Sort by Updated Date',
'books_sort_chapters_first' => 'Chapters First',
'books_sort_chapters_last' => 'Chapters Last',
'books_sort_show_other' => 'Show Other Books',
'books_sort_save' => 'Save New Order',
'books_copy' => 'Copy Book',
'books_copy_success' => 'Book successfully copied',
// Chapters
'chapter' => 'Chapter',
'chapters' => 'Chapters',
'x_chapters' => ':count Chapter|:count Chapters',
'chapters_popular' => 'Popular Chapters',
'chapters_new' => 'New Chapter',
'chapters_create' => 'Create New Chapter',
'chapters_delete' => 'Delete Chapter',
'chapters_delete_named' => 'Delete Chapter :chapterName',
'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages that exist within this chapter will also be deleted.',
'chapters_delete_confirm' => 'Are you sure you want to delete this chapter?',
'chapters_edit' => 'Edit Chapter',
'chapters_edit_named' => 'Edit Chapter :chapterName',
'chapters_save' => 'Save Chapter',
'chapters_move' => 'Move Chapter',
'chapters_move_named' => 'Move Chapter :chapterName',
'chapter_move_success' => 'Chapter moved to :bookName',
'chapters_copy' => 'Copy Chapter',
'chapters_copy_success' => 'Chapter successfully copied',
'chapters_permissions' => 'Chapter Permissions',
'chapters_empty' => 'No pages are currently in this chapter.',
'chapters_permissions_active' => 'Chapter Permissions Active',
'chapters_permissions_success' => 'Chapter Permissions Updated',
'chapters_search_this' => 'Search this chapter',
// Pages
'page' => 'Page',
'pages' => 'Pages',
'x_pages' => ':count Page|:count Pages',
'pages_popular' => 'Popular Pages',
'pages_new' => 'New Page',
'pages_attachments' => 'Attachments',
'pages_navigation' => 'Page Navigation',
'pages_delete' => 'Delete Page',
'pages_delete_named' => 'Delete Page :pageName',
'pages_delete_draft_named' => 'Delete Draft Page :pageName',
'pages_delete_draft' => 'Delete Draft Page',
'pages_delete_success' => 'Page deleted',
'pages_delete_draft_success' => 'Draft page deleted',
'pages_delete_confirm' => 'Are you sure you want to delete this page?',
'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?',
'pages_editing_named' => 'Editing Page :pageName',
'pages_edit_draft_options' => 'Draft Options',
'pages_edit_save_draft' => 'Save Draft',
'pages_edit_draft' => 'Edit Page Draft',
'pages_editing_draft' => 'Editing Draft',
'pages_editing_page' => 'Editing Page',
'pages_edit_draft_save_at' => 'Draft saved at ',
'pages_edit_delete_draft' => 'Delete Draft',
'pages_edit_discard_draft' => 'Discard Draft',
'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
'pages_edit_set_changelog' => 'Set Changelog',
'pages_edit_enter_changelog_desc' => 'Enter a brief description of the changes you\'ve made',
'pages_edit_enter_changelog' => 'Enter Changelog',
'pages_editor_switch_title' => 'Switch Editor',
'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
'pages_save' => 'Save Page',
'pages_title' => 'Page Title',
'pages_name' => 'Page Name',
'pages_md_editor' => 'Editor',
'pages_md_preview' => 'Preview',
'pages_md_insert_image' => 'Insert Image',
'pages_md_insert_link' => 'Insert Entity Link',
'pages_md_insert_drawing' => 'Insert Drawing',
'pages_not_in_chapter' => 'Page is not in a chapter',
'pages_move' => 'Move Page',
'pages_move_success' => 'Page moved to ":parentName"',
'pages_copy' => 'Copy Page',
'pages_copy_desination' => 'Copy Destination',
'pages_copy_success' => 'Page successfully copied',
'pages_permissions' => 'Page Permissions',
'pages_permissions_success' => 'Page permissions updated',
'pages_revision' => 'Revision',
'pages_revisions' => 'Page Revisions',
'pages_revisions_named' => 'Page Revisions for :pageName',
'pages_revision_named' => 'Page Revision for :pageName',
'pages_revision_restored_from' => 'Restored from #:id; :summary',
'pages_revisions_created_by' => 'Created By',
'pages_revisions_date' => 'Revision Date',
'pages_revisions_number' => '#',
'pages_revisions_numbered' => 'Revision #:id',
'pages_revisions_numbered_changes' => 'Revision #:id Changes',
'pages_revisions_editor' => 'Editor Type',
'pages_revisions_changelog' => 'Changelog',
'pages_revisions_changes' => 'Changes',
'pages_revisions_current' => 'Current Version',
'pages_revisions_preview' => 'Preview',
'pages_revisions_restore' => 'Restore',
'pages_revisions_none' => 'This page has no revisions',
'pages_copy_link' => 'Copy Link',
'pages_edit_content_link' => 'Edit Content',
'pages_permissions_active' => 'Page Permissions Active',
'pages_initial_revision' => 'Initial publish',
'pages_initial_name' => 'New Page',
'pages_editing_draft_notification' => 'You are currently editing a draft that was last saved :timeDiff.',
'pages_draft_edited_notification' => 'This page has been updated by since that time. It is recommended that you discard this draft.',
'pages_draft_page_changed_since_creation' => 'This page has been updated since this draft was created. It is recommended that you discard this draft or take care not to overwrite any page changes.',
'pages_draft_edit_active' => [
'start_a' => ':count users have started editing this page',
'start_b' => ':userName has started editing this page',
'time_a' => 'since the page was last updated',
'time_b' => 'in the last :minCount minutes',
'message' => ':start :time. Take care not to overwrite each other\'s updates!',
],
'pages_draft_discarded' => 'Draft discarded, The editor has been updated with the current page content',
'pages_specific' => 'Specific Page',
'pages_is_template' => 'Page Template',
// Editor Sidebar
'page_tags' => 'Page Tags',
'chapter_tags' => 'Chapter Tags',
'book_tags' => 'Book Tags',
'shelf_tags' => 'Shelf Tags',
'tag' => 'Tag',
'tags' => 'Tags',
'tag_name' => 'Tag Name',
'tag_value' => 'Tag Value (Optional)',
'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.",
'tags_add' => 'Add another tag',
'tags_remove' => 'Remove this tag',
'tags_usages' => 'Total tag usages',
'tags_assigned_pages' => 'Assigned to Pages',
'tags_assigned_chapters' => 'Assigned to Chapters',
'tags_assigned_books' => 'Assigned to Books',
'tags_assigned_shelves' => 'Assigned to Shelves',
'tags_x_unique_values' => ':count unique values',
'tags_all_values' => 'All values',
'tags_view_tags' => 'View Tags',
'tags_view_existing_tags' => 'View existing tags',
'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.',
'attachments' => 'Attachments',
'attachments_explain' => 'Upload some files or attach some links to display on your page. These are visible in the page sidebar.',
'attachments_explain_instant_save' => 'Changes here are saved instantly.',
'attachments_items' => 'Attached Items',
'attachments_upload' => 'Upload File',
'attachments_link' => 'Attach Link',
'attachments_set_link' => 'Set Link',
'attachments_delete' => 'Are you sure you want to delete this attachment?',
'attachments_dropzone' => 'Drop files or click here to attach a file',
'attachments_no_files' => 'No files have been uploaded',
'attachments_explain_link' => 'You can attach a link if you\'d prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.',
'attachments_link_name' => 'Link Name',
'attachment_link' => 'Attachment link',
'attachments_link_url' => 'Link to file',
'attachments_link_url_hint' => 'Url of site or file',
'attach' => 'Attach',
'attachments_insert_link' => 'Add Attachment Link to Page',
'attachments_edit_file' => 'Edit File',
'attachments_edit_file_name' => 'File Name',
'attachments_edit_drop_upload' => 'Drop files or click here to upload and overwrite',
'attachments_order_updated' => 'Attachment order updated',
'attachments_updated_success' => 'Attachment details updated',
'attachments_deleted' => 'Attachment deleted',
'attachments_file_uploaded' => 'File successfully uploaded',
'attachments_file_updated' => 'File successfully updated',
'attachments_link_attached' => 'Link successfully attached to page',
'templates' => 'Templates',
'templates_set_as_template' => 'Page is a template',
'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.',
'templates_replace_content' => 'Replace page content',
'templates_append_content' => 'Append to page content',
'templates_prepend_content' => 'Prepend to page content',
// Profile View
'profile_user_for_x' => 'User for :time',
'profile_created_content' => 'Created Content',
'profile_not_created_pages' => ':userName has not created any pages',
'profile_not_created_chapters' => ':userName has not created any chapters',
'profile_not_created_books' => ':userName has not created any books',
'profile_not_created_shelves' => ':userName has not created any shelves',
// Comments
'comment' => 'Comment',
'comments' => 'Comments',
'comment_add' => 'Add Comment',
'comment_placeholder' => 'Leave a comment here',
'comment_count' => '{0} No Comments|{1} 1 Comment|[2,*] :count Comments',
'comment_save' => 'Save Comment',
'comment_saving' => 'Saving comment...',
'comment_deleting' => 'Deleting comment...',
'comment_new' => 'New Comment',
'comment_created' => 'commented :createDiff',
'comment_updated' => 'Updated :updateDiff by :username',
'comment_deleted_success' => 'Comment deleted',
'comment_created_success' => 'Comment added',
'comment_updated_success' => 'Comment updated',
'comment_delete_confirm' => 'Are you sure you want to delete this comment?',
'comment_in_reply_to' => 'In reply to :commentId',
// Revision
'revision_delete_confirm' => 'Are you sure you want to delete this revision?',
'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
'revision_delete_success' => 'Revision deleted',
'revision_cannot_delete_latest' => 'Cannot delete the latest revision.',
// Copy view
'copy_consider' => 'Please consider the below when copying content.',
'copy_consider_permissions' => 'Custom permission settings will not be copied.',
'copy_consider_owner' => 'You will become the owner of all copied content.',
'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.',
'copy_consider_attachments' => 'Page attachments will not be copied.',
'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.',
// 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?',
];

View File

@@ -0,0 +1,109 @@
<?php
/**
* Text shown in error messaging.
*/
return [
// Permissions
'permission' => 'Nid oes gennych ganiatâd i gael mynediad i\'r dudalen y gofynnwyd amdani.',
'permissionJson' => 'Nid oes gennych ganiatâd i gyflawni\'r weithred y gofynnwyd amdani.',
// Auth
'error_user_exists_different_creds' => 'Mae defnyddiwr gyda\'r e-bost :email eisoes yn bodoli ond gyda nodweddion gwahanol.',
'email_already_confirmed' => 'E-bost eisoes wedi\'i gadarnhau, Ceisiwch fewngofnodi.',
'email_confirmation_invalid' => 'Nid yw\'r tocyn cadarnhau hwn yn ddilys neu mae eisoes wedi\'i ddefnyddio. Ceisiwch gofrestru eto.',
'email_confirmation_expired' => 'Mae\'r tocyn cadarnhad wedi dod i ben, Mae e-bost cadarnhau newydd wedi\'i anfon.',
'email_confirmation_awaiting' => 'Mae angen cadarnhau cyfeiriad e-bost y cyfrif a ddefnyddir',
'ldap_fail_anonymous' => 'Methodd mynediad LDAP gan ddefnyddio rhwymiad dienw',
'ldap_fail_authed' => 'Methodd mynediad LDAP gan ddefnyddio\'r manylion dn a chyfrinair a roddwyd',
'ldap_extension_not_installed' => 'Estyniad PHP LDAP heb ei osod',
'ldap_cannot_connect' => 'Methu cysylltu i weinydd ldap, cysylltiad cychwynnol wedi methu',
'saml_already_logged_in' => 'Wedi mewngofnodi yn barod',
'saml_user_not_registered' => 'Nid yw\'r defnyddiwr :name wedi\'i gofrestru ac mae cofrestriad awtomatig wedi\'i analluogi',
'saml_no_email_address' => 'Methu dod o hyd i gyfeiriad e-bost, ar gyfer y defnyddiwr hwn, yn y data a ddarparwyd gan y system ddilysu allanol',
'saml_invalid_response_id' => 'Nid yw\'r cais o\'r system ddilysu allanol yn cael ei gydnabod gan broses a ddechreuwyd gan y cais hwn. Gallai llywio yn ôl ar ôl mewngofnodi achosi\'r broblem hon.',
'saml_fail_authed' => 'Wedi methu mewngofnodi gan ddefnyddio :system, ni roddodd y system awdurdodiad llwyddiannus',
'oidc_already_logged_in' => 'Wedi mewngofnodi yn barod',
'oidc_user_not_registered' => 'Nid yw\'r defnyddiwr :name wedi\'i gofrestru ac mae cofrestriad awtomatig wedi\'i analluogi',
'oidc_no_email_address' => 'Methu dod o hyd i gyfeiriad e-bost, ar gyfer y defnyddiwr hwn, yn y data a ddarparwyd gan y system ddilysu allanol',
'oidc_fail_authed' => 'Wedi methu mewngofnodi gan ddefnyddio :system, ni roddodd y system awdurdodiad llwyddiannus',
'social_no_action_defined' => 'Dim gweithred wedi\'i diffinio',
'social_login_bad_response' => "Gwall a dderbyniwyd yn ystod mewngofnodi :socialAccount:\n:error",
'social_account_in_use' => 'Mae\'r cyfrif :socialAccount hwn eisoes yn cael ei ddefnyddio, Ceisiwch fewngofnodi trwy\'r opsiwn :socialAccount.',
'social_account_email_in_use' => 'Mae\'r e-bost :email eisoes yn cael ei ddefnyddio. Os oes gennych gyfrif yn barod gallwch gysylltu eich cyfrif :socialAccount o osodiadau eich proffil.',
'social_account_existing' => 'Mae\'r :socialAccount hwn eisoes ynghlwm wrth eich proffil.',
'social_account_already_used_existing' => 'Mae\'r cyfrif :socialAccount hwn eisoes yn cael ei ddefnyddio gan ddefnyddiwr arall.',
'social_account_not_used' => 'Nid yw\'r cyfrif :socialAccount hwn yn gysylltiedig ag unrhyw ddefnyddwyr. Atodwch ef yn eich gosodiadau proffil. ',
'social_account_register_instructions' => 'Os nad oes gennych gyfrif eto, gallwch gofrestru cyfrif gan ddefnyddio\'r opsiwn :socialAccount.',
'social_driver_not_found' => 'Gyrrwr cymdeithasol heb ei ganfod',
'social_driver_not_configured' => 'Nid yw eich gosodiadau cymdeithasol :socialAccount wedi\'u ffurfweddu\'n gywir.',
'invite_token_expired' => 'Mae\'r ddolen wahoddiad hon wedi dod i ben. Yn lle hynny, gallwch chi geisio ailosod cyfrinair eich cyfrif.',
// System
'path_not_writable' => 'Nid oedd modd uwchlwytho llwybr ffeil :filePath. Sicrhewch ei fod yn ysgrifenadwy i\'r gweinydd.',
'cannot_get_image_from_url' => 'Methu cael delwedd o :url',
'cannot_create_thumbs' => 'Ni all y gweinydd greu mân-luniau. Gwiriwch fod gennych yr estyniad GD PHP wedi\'i osod.',
'server_upload_limit' => 'Nid yw\'r gweinydd yn caniatáu uwchlwythiadau o\'r maint hwn. Rhowch gynnig ar faint ffeil llai.',
'uploaded' => 'Nid yw\'r gweinydd yn caniatáu uwchlwythiadau o\'r maint hwn. Rhowch gynnig ar faint ffeil llai.',
'image_upload_error' => 'Bu gwall wrth uwchlwytho\'r ddelwedd',
'image_upload_type_error' => 'Mae\'r math o ddelwedd sy\'n cael ei huwchlwytho yn annilys',
'file_upload_timeout' => 'Mae\'r amser uwchlwytho ffeil wedi dod i ben.',
// Attachments
'attachment_not_found' => 'Ni chanfuwyd yr atodiad',
// Pages
'page_draft_autosave_fail' => 'Wedi methu cadw\'r drafft. Sicrhewch fod gennych gysylltiad rhyngrwyd cyn cadw\'r dudalen hon',
'page_custom_home_deletion' => 'Methu dileu tudalen tra ei bod wedi\'i gosod fel hafan',
// Entities
'entity_not_found' => 'Endid heb ei ganfod',
'bookshelf_not_found' => 'Heb ddod o hyd i\'r silff lyfrau',
'book_not_found' => 'Ni chanfuwyd y llyfr',
'page_not_found' => 'Heb ganfod y dudalen',
'chapter_not_found' => 'Pennod heb ei chanfod',
'selected_book_not_found' => 'Ni ddaethpwyd o hyd i\'r llyfr a ddewiswyd',
'selected_book_chapter_not_found' => 'Ni ddaethpwyd o hyd i\'r Llyfr neu\'r Bennod a ddewiswyd',
'guests_cannot_save_drafts' => 'Ni all gwesteion arbed drafftiau',
// Users
'users_cannot_delete_only_admin' => 'Ni allwch ddileu\'r unig weinyddwr',
'users_cannot_delete_guest' => 'Ni allwch ddileu\'r defnyddiwr gwadd',
// Roles
'role_cannot_be_edited' => 'Nid oes modd golygu\'r rôl hon',
'role_system_cannot_be_deleted' => 'Rôl system yw\'r rôl hon ac ni ellir ei dileu',
'role_registration_default_cannot_delete' => 'Ni ellir dileu\'r rôl hon tra ei bod wedi\'i gosod fel y rôl gofrestru ddiofyn',
'role_cannot_remove_only_admin' => 'Y defnyddiwr hwn yw\'r unig ddefnyddiwr sydd wedi\'i neilltuo i rôl y gweinyddwr. Neilltuo rôl y gweinyddwr i ddefnyddiwr arall cyn ceisio ei dynnu yma.',
// Comments
'comment_list' => 'Digwyddodd gwall wrth nôl y sylwadau.',
'cannot_add_comment_to_draft' => 'Ni allwch ychwanegu sylwadau at ddrafft.',
'comment_add' => 'Digwyddodd gwall wrth ychwanegu / diweddaru\'r sylw.',
'comment_delete' => 'Digwyddodd gwall wrth dileu\'r sylwad.',
'empty_comment' => 'Methu ychwanegu sylw gwag.',
// Error pages
'404_page_not_found' => 'Heb ganfod y dudalen',
'sorry_page_not_found' => 'Mae\'n ddrwg gennym, nid oedd modd dod o hyd i\'r dudalen roeddech yn chwilio amdani.',
'sorry_page_not_found_permission_warning' => 'Os oeddech yn disgwyl i\'r dudalen hon fodoli, efallai na fyddai gennych ganiatâd i\'w gweld.',
'image_not_found' => 'Heb ganfod y delwedd',
'image_not_found_subtitle' => 'Mae\'n ddrwg gennym, ni fu modd dod o hyd i\'r ffeil delwedd roeddech yn chwilio amdani.',
'image_not_found_details' => 'Os oeddech chi\'n disgwyl i\'r ddelwedd hon fodoli efallai ei bod wedi\'i dileu.',
'return_home' => 'Dychwelyd i gartref',
'error_occurred' => 'Digwyddodd Gwall',
'app_down' => 'Mae :appName i lawr ar hyn o bryd',
'back_soon' => 'Bydd yn ôl i fyny yn fuan.',
// API errors
'api_no_authorization_found' => 'Ni chanfuwyd tocyn awdurdodi ar y cais',
'api_bad_authorization_format' => 'Canfuwyd tocyn awdurdodi ar y cais ond roedd yn ymddangos bod y fformat yn anghywir',
'api_user_token_not_found' => 'Ni chanfuwyd tocyn API cyfatebol ar gyfer y tocyn awdurdodi a ddarparwyd',
'api_incorrect_token_secret' => 'Mae\'r gyfrinach a ddarparwyd ar gyfer y tocyn API defnyddiedig a roddwyd yn anghywir',
'api_user_no_api_permission' => 'Nid oes gan berchennog y tocyn API a ddefnyddiwyd ganiatâd i wneud galwadau API',
'api_user_token_expired' => 'Mae\'r tocyn awdurdodi a ddefnyddiwyd wedi dod i ben',
// Settings & Maintenance
'maintenance_test_email_failure' => 'Gwall a daflwyd wrth anfon e-bost prawf:',
];

View File

@@ -0,0 +1,12 @@
<?php
/**
* Pagination Language Lines
* The following language lines are used by the paginator library to build
* the simple pagination links.
*/
return [
'previous' => '&laquo; Previous',
'next' => 'Next &raquo;',
];

View File

@@ -0,0 +1,15 @@
<?php
/**
* Password Reminder Language Lines
* The following language lines are the default lines which match reasons
* that are given by the password broker for a password update attempt has failed.
*/
return [
'password' => 'Passwords must be at least eight characters and match the confirmation.',
'user' => "We can't find a user with that e-mail address.",
'token' => 'The password reset token is invalid for this email address.',
'sent' => 'We have e-mailed your password reset link!',
'reset' => 'Your password has been reset!',
];

View File

@@ -0,0 +1,309 @@
<?php
/**
* Settings text strings
* Contains all text strings used in the general settings sections of BookStack
* including users and roles.
*/
return [
// Common Messages
'settings' => 'Settings',
'settings_save' => 'Save Settings',
'settings_save_success' => 'Settings saved',
'system_version' => 'System Version',
'categories' => 'Categories',
// App Settings
'app_customization' => 'Customization',
'app_features_security' => 'Features & Security',
'app_name' => 'Application Name',
'app_name_desc' => 'This name is shown in the header and in any system-sent emails.',
'app_name_header' => 'Show name in header',
'app_public_access' => 'Public Access',
'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.',
'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.',
'app_public_access_toggle' => 'Allow public access',
'app_public_viewing' => 'Allow public viewing?',
'app_secure_images' => 'Higher Security Image Uploads',
'app_secure_images_toggle' => 'Enable higher security image uploads',
'app_secure_images_desc' => 'For performance reasons, all images are public. This option adds a random, hard-to-guess string in front of image urls. Ensure directory indexes are not enabled to prevent easy access.',
'app_default_editor' => 'Default Page Editor',
'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
'app_custom_html' => 'Custom HTML Head Content',
'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the <head> section of every page. This is handy for overriding styles or adding analytics code.',
'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
'app_logo' => 'Application Logo',
'app_logo_desc' => 'This image should be 43px in height. <br>Large images will be scaled down.',
'app_primary_color' => 'Application Primary Color',
'app_primary_color_desc' => 'Sets the primary color for the application including the banner, buttons, and links.',
'app_homepage' => 'Application Homepage',
'app_homepage_desc' => 'Select a view to show on the homepage instead of the default view. Page permissions are ignored for selected pages.',
'app_homepage_select' => 'Select a page',
'app_footer_links' => 'Footer Links',
'app_footer_links_desc' => 'Add links to show within the site footer. These will be displayed at the bottom of most pages, including those that do not require login. You can use a label of "trans::<key>" to use system-defined translations. For example: Using "trans::common.privacy_policy" will provide the translated text "Privacy Policy" and "trans::common.terms_of_service" will provide the translated text "Terms of Service".',
'app_footer_links_label' => 'Link Label',
'app_footer_links_url' => 'Link URL',
'app_footer_links_add' => 'Add Footer Link',
'app_disable_comments' => 'Disable Comments',
'app_disable_comments_toggle' => 'Disable comments',
'app_disable_comments_desc' => 'Disables comments across all pages in the application. <br> Existing comments are not shown.',
// Color settings
'content_colors' => 'Content Colors',
'content_colors_desc' => 'Sets colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.',
'bookshelf_color' => 'Shelf Color',
'book_color' => 'Book Color',
'chapter_color' => 'Chapter Color',
'page_color' => 'Page Color',
'page_draft_color' => 'Page Draft Color',
// Registration Settings
'reg_settings' => 'Registration',
'reg_enable' => 'Enable Registration',
'reg_enable_toggle' => 'Enable registration',
'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
'reg_default_role' => 'Default user role after registration',
'reg_enable_external_warning' => 'The option above is ignored while external LDAP or SAML authentication is active. User accounts for non-existing members will be auto-created if authentication, against the external system in use, is successful.',
'reg_email_confirmation' => 'Email Confirmation',
'reg_email_confirmation_toggle' => 'Require email confirmation',
'reg_confirm_email_desc' => 'If domain restriction is used then email confirmation will be required and this option will be ignored.',
'reg_confirm_restrict_domain' => 'Domain Restriction',
'reg_confirm_restrict_domain_desc' => 'Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application. <br> Note that users will be able to change their email addresses after successful registration.',
'reg_confirm_restrict_domain_placeholder' => 'No restriction set',
// Maintenance settings
'maint' => 'Maintenance',
'maint_image_cleanup' => 'Cleanup Images',
'maint_image_cleanup_desc' => 'Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.',
'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
'maint_image_cleanup_run' => 'Run Cleanup',
'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?',
'maint_image_cleanup_success' => ':count potentially unused images found and deleted!',
'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!',
'maint_send_test_email' => 'Send a Test Email',
'maint_send_test_email_desc' => 'This sends a test email to your email address specified in your profile.',
'maint_send_test_email_run' => 'Send test email',
'maint_send_test_email_success' => 'Email sent to :address',
'maint_send_test_email_mail_subject' => 'Test Email',
'maint_send_test_email_mail_greeting' => 'Email delivery seems to work!',
'maint_send_test_email_mail_text' => 'Congratulations! As you received this email notification, your email settings seem to be configured properly.',
'maint_recycle_bin_desc' => 'Deleted shelves, books, chapters & pages are sent to the recycle bin so they can be restored or permanently deleted. Older items in the recycle bin may be automatically removed after a while depending on system configuration.',
'maint_recycle_bin_open' => 'Open Recycle Bin',
// Recycle Bin
'recycle_bin' => 'Recycle Bin',
'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
'recycle_bin_deleted_item' => 'Deleted Item',
'recycle_bin_deleted_parent' => 'Parent',
'recycle_bin_deleted_by' => 'Deleted By',
'recycle_bin_deleted_at' => 'Deletion Time',
'recycle_bin_permanently_delete' => 'Permanently Delete',
'recycle_bin_restore' => 'Restore',
'recycle_bin_contents_empty' => 'The recycle bin is currently empty',
'recycle_bin_empty' => 'Empty Recycle Bin',
'recycle_bin_empty_confirm' => 'This will permanently destroy all items in the recycle bin including content contained within each item. Are you sure you want to empty the recycle bin?',
'recycle_bin_destroy_confirm' => 'This action will permanently delete this item, along with any child elements listed below, from the system and you will not be able to restore this content. Are you sure you want to permanently delete this item?',
'recycle_bin_destroy_list' => 'Items to be Destroyed',
'recycle_bin_restore_list' => 'Items to be Restored',
'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.',
'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.',
'recycle_bin_restore_parent' => 'Restore Parent',
'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.',
'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
// Audit Log
'audit' => 'Audit Log',
'audit_desc' => 'This audit log displays a list of activities tracked in the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.',
'audit_event_filter' => 'Event Filter',
'audit_event_filter_no_filter' => 'No Filter',
'audit_deleted_item' => 'Deleted Item',
'audit_deleted_item_name' => 'Name: :name',
'audit_table_user' => 'User',
'audit_table_event' => 'Event',
'audit_table_related' => 'Related Item or Detail',
'audit_table_ip' => 'IP Address',
'audit_table_date' => 'Activity Date',
'audit_date_from' => 'Date Range From',
'audit_date_to' => 'Date Range To',
// Role Settings
'roles' => 'Roles',
'role_user_roles' => 'User Roles',
'role_create' => 'Create New Role',
'role_create_success' => 'Role successfully created',
'role_delete' => 'Delete Role',
'role_delete_confirm' => 'This will delete the role with the name \':roleName\'.',
'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.',
'role_delete_no_migration' => "Don't migrate users",
'role_delete_sure' => 'Are you sure you want to delete this role?',
'role_delete_success' => 'Role successfully deleted',
'role_edit' => 'Edit Role',
'role_details' => 'Role Details',
'role_name' => 'Role Name',
'role_desc' => 'Short Description of Role',
'role_mfa_enforced' => 'Requires Multi-Factor Authentication',
'role_external_auth_id' => 'External Authentication IDs',
'role_system' => 'System Permissions',
'role_manage_users' => 'Manage users',
'role_manage_roles' => 'Manage roles & role permissions',
'role_manage_entity_permissions' => 'Manage all book, chapter & page permissions',
'role_manage_own_entity_permissions' => 'Manage permissions on own book, chapter & pages',
'role_manage_page_templates' => 'Manage page templates',
'role_access_api' => 'Access system API',
'role_manage_settings' => 'Manage app settings',
'role_export_content' => 'Export content',
'role_editor_change' => 'Change page editor',
'role_asset' => 'Asset Permissions',
'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.',
'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.',
'role_all' => 'All',
'role_own' => 'Own',
'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
'role_save' => 'Save Role',
'role_update_success' => 'Role successfully updated',
'role_users' => 'Users in this role',
'role_users_none' => 'No users are currently assigned to this role',
// Users
'users' => 'Users',
'user_profile' => 'User Profile',
'users_add_new' => 'Add New User',
'users_search' => 'Search Users',
'users_latest_activity' => 'Latest Activity',
'users_details' => 'User Details',
'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.',
'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.',
'users_role' => 'User Roles',
'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.',
'users_password' => 'User Password',
'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 8 characters long.',
'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.',
'users_send_invite_option' => 'Send user invite email',
'users_external_auth_id' => 'External Authentication ID',
'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your external authentication system.',
'users_password_warning' => 'Only fill the below if you would like to change your password.',
'users_system_public' => 'This user represents any guest users that visit your instance. It cannot be used to log in but is assigned automatically.',
'users_delete' => 'Delete User',
'users_delete_named' => 'Delete user :userName',
'users_delete_warning' => 'This will fully delete this user with the name \':userName\' from the system.',
'users_delete_confirm' => 'Are you sure you want to delete this user?',
'users_migrate_ownership' => 'Migrate Ownership',
'users_migrate_ownership_desc' => 'Select a user here if you want another user to become the owner of all items currently owned by this user.',
'users_none_selected' => 'No user selected',
'users_edit' => 'Edit User',
'users_edit_profile' => 'Edit Profile',
'users_avatar' => 'User Avatar',
'users_avatar_desc' => 'Select an image to represent this user. This should be approx 256px square.',
'users_preferred_language' => 'Preferred Language',
'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.',
'users_social_accounts' => 'Social Accounts',
'users_social_accounts_info' => 'Here you can connect your other accounts for quicker and easier login. Disconnecting an account here does not revoke previously authorized access. Revoke access from your profile settings on the connected social account.',
'users_social_connect' => 'Connect Account',
'users_social_disconnect' => 'Disconnect Account',
'users_social_connected' => ':socialAccount account was successfully attached to your profile.',
'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.',
'users_api_tokens' => 'API Tokens',
'users_api_tokens_none' => 'No API tokens have been created for this user',
'users_api_tokens_create' => 'Create Token',
'users_api_tokens_expires' => 'Expires',
'users_api_tokens_docs' => 'API Documentation',
'users_mfa' => 'Multi-Factor Authentication',
'users_mfa_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.',
'users_mfa_x_methods' => ':count method configured|:count methods configured',
'users_mfa_configure' => 'Configure Methods',
// API Tokens
'user_api_token_create' => 'Create API Token',
'user_api_token_name' => 'Name',
'user_api_token_name_desc' => 'Give your token a readable name as a future reminder of its intended purpose.',
'user_api_token_expiry' => 'Expiry Date',
'user_api_token_expiry_desc' => 'Set a date at which this token expires. After this date, requests made using this token will no longer work. Leaving this field blank will set an expiry 100 years into the future.',
'user_api_token_create_secret_message' => 'Immediately after creating this token a "Token ID" & "Token Secret" will be generated and displayed. The secret will only be shown a single time so be sure to copy the value to somewhere safe and secure before proceeding.',
'user_api_token_create_success' => 'API token successfully created',
'user_api_token_update_success' => 'API token successfully updated',
'user_api_token' => 'API Token',
'user_api_token_id' => 'Token ID',
'user_api_token_id_desc' => 'This is a non-editable system generated identifier for this token which will need to be provided in API requests.',
'user_api_token_secret' => 'Token Secret',
'user_api_token_secret_desc' => 'This is a system generated secret for this token which will need to be provided in API requests. This will only be displayed this one time so copy this value to somewhere safe and secure.',
'user_api_token_created' => 'Token created :timeAgo',
'user_api_token_updated' => 'Token updated :timeAgo',
'user_api_token_delete' => 'Delete Token',
'user_api_token_delete_warning' => 'This will fully delete this API token with the name \':tokenName\' from the system.',
'user_api_token_delete_confirm' => 'Are you sure you want to delete this API token?',
'user_api_token_delete_success' => 'API token successfully deleted',
// Webhooks
'webhooks' => 'Webhooks',
'webhooks_create' => 'Create New Webhook',
'webhooks_none_created' => 'No webhooks have yet been created.',
'webhooks_edit' => 'Edit Webhook',
'webhooks_save' => 'Save Webhook',
'webhooks_details' => 'Webhook Details',
'webhooks_details_desc' => 'Provide a user friendly name and a POST endpoint as a location for the webhook data to be sent to.',
'webhooks_events' => 'Webhook Events',
'webhooks_events_desc' => 'Select all the events that should trigger this webhook to be called.',
'webhooks_events_warning' => 'Keep in mind that these events will be triggered for all selected events, even if custom permissions are applied. Ensure that use of this webhook won\'t expose confidential content.',
'webhooks_events_all' => 'All system events',
'webhooks_name' => 'Webhook Name',
'webhooks_timeout' => 'Webhook Request Timeout (Seconds)',
'webhooks_endpoint' => 'Webhook Endpoint',
'webhooks_active' => 'Webhook Active',
'webhook_events_table_header' => 'Events',
'webhooks_delete' => 'Delete Webhook',
'webhooks_delete_warning' => 'This will fully delete this webhook, with the name \':webhookName\', from the system.',
'webhooks_delete_confirm' => 'Are you sure you want to delete this webhook?',
'webhooks_format_example' => 'Webhook Format Example',
'webhooks_format_example_desc' => 'Webhook data is sent as a POST request to the configured endpoint as JSON following the format below. The "related_item" and "url" properties are optional and will depend on the type of event triggered.',
'webhooks_status' => 'Webhook Status',
'webhooks_last_called' => 'Last Called:',
'webhooks_last_errored' => 'Last Errored:',
'webhooks_last_error_message' => 'Last Error Message:',
//! If editing translations files directly please ignore this in all
//! languages apart from en. Content will be auto-copied from en.
//!////////////////////////////////
'language_select' => [
'en' => 'English',
'ar' => 'العربية',
'bg' => 'Bǎlgarski',
'bs' => 'Bosanski',
'ca' => 'Català',
'cs' => 'Česky',
'da' => 'Dansk',
'de' => 'Deutsch (Sie)',
'de_informal' => 'Deutsch (Du)',
'es' => 'Español',
'es_AR' => 'Español Argentina',
'et' => 'Eesti keel',
'eu' => 'Euskara',
'fa' => 'فارسی',
'fr' => 'Français',
'he' => 'עברית',
'hr' => 'Hrvatski',
'hu' => 'Magyar',
'id' => 'Bahasa Indonesia',
'it' => 'Italian',
'ja' => '日本語',
'ko' => '한국어',
'lt' => 'Lietuvių Kalba',
'lv' => 'Latviešu Valoda',
'nl' => 'Nederlands',
'nb' => 'Norsk (Bokmål)',
'pl' => 'Polski',
'pt' => 'Português',
'pt_BR' => 'Português do Brasil',
'ru' => 'Русский',
'sk' => 'Slovensky',
'sl' => 'Slovenščina',
'sv' => 'Svenska',
'tr' => 'Türkçe',
'uk' => 'Українська',
'vi' => 'Tiếng Việt',
'zh_CN' => '简体中文',
'zh_TW' => '繁體中文',
],
//!////////////////////////////////
];

View File

@@ -0,0 +1,117 @@
<?php
/**
* Validation Lines
* The following language lines contain the default error messages used by
* the validator class. Some of these rules have multiple versions such
* as the size rules. Feel free to tweak each of these messages here.
*/
return [
// Standard laravel validation lines
'accepted' => 'Rhaid derbyn y :attribute.',
'active_url' => 'Nid ywr :attribute yn URL dilys.',
'after' => 'Rhaid i\'r :attribute bod yn dyddiad ar ol :date.',
'alpha' => 'Rhaid ir :attribute cynnwys llythrennau yn unig.',
'alpha_dash' => 'Dim ond llythrennau, rhifau, llinellau toriad a thanlinellau y gall y :attribute gynnwys.',
'alpha_num' => 'Rhaid ir :attribute cynnwys llythrennau a rhifau yn unig.',
'array' => 'Rhaid i :attribute fod yn array.',
'backup_codes' => 'Nid yw\'r cod a ddarparwyd yn ddilys neu mae eisoes wedi\'i ddefnyddio.',
'before' => 'Rhaid i\'r :attribute bod yn dyddiad cyn :date.',
'between' => [
'numeric' => 'Rhaid i\'r :attribute bod rhwng :min a :max.',
'file' => 'Rhaid i\'r :attribute bod rhwng :min a :max kilobytes.',
'string' => 'Rhaid i\'r :attribute bod rhwng :min a :max cymeriadau.',
'array' => 'Rhaid i\'r :attribute cael rhwng :min a :max o eitemau.',
],
'boolean' => 'Rhaid i :attribute fod yn wir neu ddim.',
'confirmed' => 'Dydi\'r cadarnhad :attribute ddim yn cydfynd.',
'date' => 'Nid yw\'r :attribute yn dyddiad dilys.',
'date_format' => 'Nid yw\'r :attribute yn cydfynd ar format :format.',
'different' => 'Rhaid i :attribute a :other bod yn wahanol.',
'digits' => 'Rhai i\'r :attribute bod yn :digits o ddigidau.',
'digits_between' => 'Rhaid i\'r :attribute bod rhwng :min a :max o digidau.',
'email' => 'Rhaid i\'r :attribute bod yn cyfeiriad e-bost dilys.',
'ends_with' => 'Rhaid i\'r :attribute orffen gydag un o\'r canlynol: :values',
'file' => 'Rhaid darparu\'r :attribute fel ffeil ddilys.',
'filled' => 'Mae angen llenwi\'r maes :attribute.',
'gt' => [
'numeric' => 'Rhaid i\'r :attribute fod yn fwy na :value.',
'file' => 'Rhaid i\'r :attribute fod yn fwy na :value kilobytes.',
'string' => 'Rhaid i\'r :attribute fod yn fwy na :value cymeriadau.',
'array' => 'Rhaid i\'r :attribute fod yn fwy na :value eitemau.',
],
'gte' => [
'numeric' => 'The :attribute must be greater than or equal :value.',
'file' => 'The :attribute must be greater than or equal :value kilobytes.',
'string' => 'The :attribute must be greater than or equal :value characters.',
'array' => 'The :attribute must have :value items or more.',
],
'exists' => 'The selected :attribute is invalid.',
'image' => 'The :attribute must be an image.',
'image_extension' => 'The :attribute must have a valid & supported image extension.',
'in' => 'The selected :attribute is invalid.',
'integer' => 'The :attribute must be an integer.',
'ip' => 'The :attribute must be a valid IP address.',
'ipv4' => 'The :attribute must be a valid IPv4 address.',
'ipv6' => 'The :attribute must be a valid IPv6 address.',
'json' => 'The :attribute must be a valid JSON string.',
'lt' => [
'numeric' => 'The :attribute must be less than :value.',
'file' => 'The :attribute must be less than :value kilobytes.',
'string' => 'The :attribute must be less than :value characters.',
'array' => 'The :attribute must have less than :value items.',
],
'lte' => [
'numeric' => 'The :attribute must be less than or equal :value.',
'file' => 'The :attribute must be less than or equal :value kilobytes.',
'string' => 'The :attribute must be less than or equal :value characters.',
'array' => 'The :attribute must not have more than :value items.',
],
'max' => [
'numeric' => 'The :attribute may not be greater than :max.',
'file' => 'The :attribute may not be greater than :max kilobytes.',
'string' => 'The :attribute may not be greater than :max characters.',
'array' => 'The :attribute may not have more than :max items.',
],
'mimes' => 'The :attribute must be a file of type: :values.',
'min' => [
'numeric' => 'The :attribute must be at least :min.',
'file' => 'The :attribute must be at least :min kilobytes.',
'string' => 'The :attribute must be at least :min characters.',
'array' => 'The :attribute must have at least :min items.',
],
'not_in' => 'The selected :attribute is invalid.',
'not_regex' => 'The :attribute format is invalid.',
'numeric' => 'The :attribute must be a number.',
'regex' => 'The :attribute format is invalid.',
'required' => 'The :attribute field is required.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values is present.',
'required_without' => 'The :attribute field is required when :values is not present.',
'required_without_all' => 'The :attribute field is required when none of :values are present.',
'same' => 'The :attribute and :other must match.',
'safe_url' => 'The provided link may not be safe.',
'size' => [
'numeric' => 'The :attribute must be :size.',
'file' => 'The :attribute must be :size kilobytes.',
'string' => 'The :attribute must be :size characters.',
'array' => 'The :attribute must contain :size items.',
],
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid zone.',
'totp' => 'The provided code is not valid or has expired.',
'unique' => 'The :attribute has already been taken.',
'url' => 'The :attribute format is invalid.',
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
// Custom validation lines
'custom' => [
'password-confirm' => [
'required_with' => 'Password confirmation required',
],
],
// Custom validation attributes
'attributes' => [],
];

View File

@@ -7,41 +7,45 @@ return [
// Pages
'page_create' => 'oprettede side',
'page_create_notification' => 'Page successfully created',
'page_create_notification' => 'Siden blev oprettet',
'page_update' => 'opdaterede side',
'page_update_notification' => 'Page successfully updated',
'page_update_notification' => 'Siden blev opdateret',
'page_delete' => 'slettede side',
'page_delete_notification' => 'Page successfully deleted',
'page_delete_notification' => 'Siden blev slettet',
'page_restore' => 'gendannede side',
'page_restore_notification' => 'Page successfully restored',
'page_restore_notification' => 'Siden blev gendannet',
'page_move' => 'flyttede side',
// Chapters
'chapter_create' => 'oprettede kapitel',
'chapter_create_notification' => 'Chapter successfully created',
'chapter_create_notification' => 'Kapitel blev oprettet',
'chapter_update' => 'opdaterede kapitel',
'chapter_update_notification' => 'Chapter successfully updated',
'chapter_update_notification' => 'Kapitel blev opdateret',
'chapter_delete' => 'slettede kapitel',
'chapter_delete_notification' => 'Chapter successfully deleted',
'chapter_delete_notification' => 'Kapitel blev slettet',
'chapter_move' => 'flyttede kapitel',
// Books
'book_create' => 'oprettede bog',
'book_create_notification' => 'Book successfully created',
'book_create_notification' => 'Bogen blev oprettet',
'book_create_from_chapter' => 'omdannede kapitel til bog',
'book_create_from_chapter_notification' => 'Kapitel blev omdannet til en bog',
'book_update' => 'opdaterede bog',
'book_update_notification' => 'Book successfully updated',
'book_update_notification' => 'Bogen blev opdateret',
'book_delete' => 'slettede bog',
'book_delete_notification' => 'Book successfully deleted',
'book_delete_notification' => 'Bogen blev slettet',
'book_sort' => 'sorterede bogen',
'book_sort_notification' => 'Book successfully re-sorted',
'book_sort_notification' => 'Bogen blev re-sorteret',
// Bookshelves
'bookshelf_create' => 'created bookshelf',
'bookshelf_create_notification' => 'Bookshelf successfully created',
'bookshelf_create' => 'oprettede bogreol',
'bookshelf_create_notification' => 'Bogreolen blev oprettet',
'bookshelf_create_from_book' => 'omdannede bog til bogreol',
'bookshelf_create_from_book_notification' => 'Bogen blev omdannet til en bogreal',
'bookshelf_update' => 'opdaterede bogreolen',
'bookshelf_update_notification' => 'Bookshelf successfully updated',
'bookshelf_update_notification' => 'Bogreolen blev opdateret',
'bookshelf_delete' => 'slettede bogreol',
'bookshelf_delete_notification' => 'Bookshelf successfully deleted',
'bookshelf_delete_notification' => 'Bogreolen blev slettet',
// Favourites
'favourite_add_notification' => '":name" er blevet tilføjet til dine favoritter',
@@ -52,16 +56,16 @@ return [
'mfa_remove_method_notification' => 'Multi-faktor metode fjernet',
// 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' => 'oprettede webhook',
'webhook_create_notification' => 'Webhooken blev oprettet',
'webhook_update' => 'opdaterede webhooken',
'webhook_update_notification' => 'Webhooken blev opdateret',
'webhook_delete' => 'slettede webhooken',
'webhook_delete_notification' => 'Webhooken blev slettet',
// Users
'user_update_notification' => 'User successfully updated',
'user_delete_notification' => 'User successfully removed',
'user_update_notification' => 'Brugeren blev opdateret',
'user_delete_notification' => 'Brugeren blev fjernet',
// Other
'commented_on' => 'kommenterede til',

View File

@@ -21,7 +21,7 @@ return [
'email' => 'E-mail',
'password' => 'Adgangskode',
'password_confirm' => 'Bekræft adgangskode',
'password_hint' => 'Must be at least 8 characters',
'password_hint' => 'Skal være på mindst 8 karakterer',
'forgot_password' => 'Glemt Adgangskode?',
'remember_me' => 'Husk mig',
'ldap_email_hint' => 'Angiv venligst din kontos e-mail.',
@@ -38,6 +38,11 @@ return [
'registration_email_domain_invalid' => 'E-Mail domænet har ikke adgang til denne applikation',
'register_success' => 'Tak for din registrering. Du er nu registeret og logget ind.',
// 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' => 'Nulstil adgangskode',
'reset_password_send_instructions' => 'Indtast din E-Mail herunder og du vil blive sendt en E-Mail med et link til at nulstille din adgangskode.',
@@ -54,7 +59,7 @@ return [
'email_confirm_text' => 'Bekræft venligst din E-Mail adresse ved at klikke på linket nedenfor:',
'email_confirm_action' => 'Bekræft E-Mail',
'email_confirm_send_error' => 'E-Mail-bekræftelse kræves, men systemet kunne ikke sende E-Mailen. Kontakt administratoren for at sikre, at E-Mail er konfigureret korrekt.',
'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.',
'email_confirm_success' => 'Din email er blevet bekræftet! Du bør nu kune logge ind med denne emailadresse.',
'email_confirm_resent' => 'Bekræftelsesmail sendt, tjek venligst din indboks.',
'email_not_confirmed' => 'E-Mail adresse ikke bekræftet',
@@ -71,7 +76,7 @@ return [
'user_invite_page_welcome' => 'Velkommen til :appName!',
'user_invite_page_text' => 'For at færdiggøre din konto og få adgang skal du indstille en adgangskode, der bruges til at logge ind på :appName ved fremtidige besøg.',
'user_invite_page_confirm_button' => 'Bekræft adgangskode',
'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!',
'user_invite_success_login' => 'Adgangskoden er sat. Du burde nu kunne logge ind med din angivede adgangskode for at tilgå :appName!',
// Multi-factor Authentication
'mfa_setup' => 'Opsætning af Multi-faktor godkendelse',
@@ -88,23 +93,23 @@ return [
'mfa_gen_confirm_and_enable' => 'Bekræft og aktivér',
'mfa_gen_backup_codes_title' => 'Backup koder opsætning',
'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_backup_codes_download' => 'Download koder',
'mfa_gen_backup_codes_usage_warning' => 'Hver kode kan kun bruges en gang',
'mfa_gen_totp_title' => 'Mobil 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' => 'Verificer Opsætning',
'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' => 'Ingen Metoder Konfigureret',
'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_use_totp' => 'Bekræft ved brug af en mobil app',
'mfa_verify_use_backup_codes' => 'Bekræft ved hjælp af en backup kode',
'mfa_verify_backup_code' => 'Backup Kode',
'mfa_verify_backup_code_desc' => 'Indtast en af dine resterende backup koder nedenfor:',
'mfa_verify_backup_code_enter_here' => 'Indtast backup kode her',
'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.',
];

View File

@@ -45,8 +45,10 @@ return [
'unfavourite' => 'Fjern som foretrukken',
'next' => 'Næste',
'previous' => 'Forrige',
'filter_active' => 'Active Filter:',
'filter_clear' => 'Clear Filter',
'filter_active' => 'Aktivt Filter:',
'filter_clear' => 'Nulstil Filter',
'download' => 'Hent',
'open_in_tab' => 'Åben i ny fane',
// Sort Options
'sort_options' => 'Sorteringsindstillinger',
@@ -72,10 +74,10 @@ return [
'default' => 'Standard',
'breadcrumb' => 'Brødkrumme',
'status' => 'Status',
'status_active' => 'Active',
'status_inactive' => 'Inactive',
'never' => 'Never',
'none' => 'None',
'status_active' => 'Aktiv',
'status_inactive' => 'Inaktiv',
'never' => 'Aldrig',
'none' => 'Ingen',
// Header
'header_menu_expand' => 'Udvid header menu',

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