mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-17 03:12:16 +03:00
Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02dfe11ce6 | ||
|
|
83d06beb70 | ||
|
|
d2a9b312e9 | ||
|
|
a8cfc059c8 | ||
|
|
1614b2bab0 | ||
|
|
01260d95f3 | ||
|
|
d69ba6b47a | ||
|
|
098128aafb | ||
|
|
92c9837157 | ||
|
|
18e5f86ffa | ||
|
|
c860645a5a | ||
|
|
fcb93dc7c8 | ||
|
|
fcdb39e428 | ||
|
|
1b3e1863f4 | ||
|
|
fbc2175789 | ||
|
|
8099c431bb | ||
|
|
efbfe0f7af | ||
|
|
66402b474c | ||
|
|
f47f0e05d6 | ||
|
|
4bdec0d214 | ||
|
|
6a7d7e7c2b | ||
|
|
c83a51f7e2 | ||
|
|
b922c8029e | ||
|
|
653761e67d | ||
|
|
d59ff132ab | ||
|
|
e6e740b2a1 | ||
|
|
af6f4e6c8c | ||
|
|
69a0f8d502 | ||
|
|
6d35fb5237 | ||
|
|
79d0f707e6 | ||
|
|
369dc02e78 | ||
|
|
9d2e65b73d | ||
|
|
f421d83627 | ||
|
|
be2ca9d4bb | ||
|
|
17bca662a7 | ||
|
|
1776204870 | ||
|
|
985e214d94 | ||
|
|
2bcc159fd6 | ||
|
|
fb7c12438d | ||
|
|
b2cd363539 | ||
|
|
f668bee88b | ||
|
|
642f2760cc | ||
|
|
37aa8b05f8 | ||
|
|
d640cc1eee | ||
|
|
c2d6e98985 | ||
|
|
84b4fe6176 | ||
|
|
decdf5714b | ||
|
|
9da600caf9 | ||
|
|
45aee2a1c1 | ||
|
|
f5df5ac7d5 | ||
|
|
fb29f4119d | ||
|
|
93795b6eda | ||
|
|
f7b808a9e6 | ||
|
|
448068e318 | ||
|
|
7d81a95156 | ||
|
|
a9bf2ed398 | ||
|
|
771f781e7f | ||
|
|
78be8535f7 | ||
|
|
6c4c1ccb58 | ||
|
|
562225a77b | ||
|
|
b936e1f403 | ||
|
|
b3cc3130f0 | ||
|
|
0363fc4ea1 | ||
|
|
134a96fa32 | ||
|
|
56f444a8a7 | ||
|
|
30d4674657 | ||
|
|
9f961f95f8 | ||
|
|
86f43c8a65 | ||
|
|
d886c6a32e | ||
|
|
f399e60910 | ||
|
|
173eaf1c98 | ||
|
|
64eabaf882 | ||
|
|
6b84a76af1 | ||
|
|
2bd6ba9895 | ||
|
|
1df0bcaf85 | ||
|
|
c31e6a03ce | ||
|
|
61c9324229 | ||
|
|
8c4c8cd95b | ||
|
|
0c9c1e4c6b | ||
|
|
9ec114641c | ||
|
|
295c7918a4 | ||
|
|
3ac34b5849 | ||
|
|
6e7adcc095 | ||
|
|
a1ecdcacba | ||
|
|
019b8196ad | ||
|
|
63f96c1c6f | ||
|
|
8df9dab80a | ||
|
|
93147f4340 | ||
|
|
77727e7e50 | ||
|
|
9f4c64a676 | ||
|
|
e0ebae19aa | ||
|
|
6cdb943916 | ||
|
|
d3d8ddbe52 | ||
|
|
57c312ec3f | ||
|
|
13ad0031d6 | ||
|
|
d5b922aa50 | ||
|
|
28823c4fae | ||
|
|
b6bb078e0a | ||
|
|
8254c3be8d | ||
|
|
47cb99a2d6 | ||
|
|
86b2ddbd28 | ||
|
|
2e4863edb1 | ||
|
|
b0d027a4a9 | ||
|
|
0c3c8fc9c3 | ||
|
|
624c568008 | ||
|
|
58a0a59d7e | ||
|
|
3d0d7f8be2 | ||
|
|
6c5304a3de |
2
.browserslistrc
Normal file
2
.browserslistrc
Normal file
@@ -0,0 +1,2 @@
|
||||
>0.25%
|
||||
not op_mini all
|
||||
@@ -56,6 +56,8 @@ TWITCH_APP_SECRET=false
|
||||
GITLAB_APP_ID=false
|
||||
GITLAB_APP_SECRET=false
|
||||
GITLAB_BASE_URI=false
|
||||
DISCORD_APP_ID=false
|
||||
DISCORD_APP_SECRET=false
|
||||
|
||||
# External services such as Gravatar and Draw.IO
|
||||
DISABLE_EXTERNAL_SERVICES=false
|
||||
@@ -67,6 +69,13 @@ LDAP_DN=false
|
||||
LDAP_PASS=false
|
||||
LDAP_USER_FILTER=false
|
||||
LDAP_VERSION=false
|
||||
# Do you want to sync LDAP groups to BookStack roles for a user
|
||||
LDAP_USER_TO_GROUPS=false
|
||||
# What is the LDAP attribute for group memberships
|
||||
LDAP_GROUP_ATTRIBUTE="memberOf"
|
||||
# Would you like to remove users from roles on BookStack if they do not match on LDAP
|
||||
# If false, the ldap groups-roles sync will only add users to roles
|
||||
LDAP_REMOVE_FROM_GROUPS=false
|
||||
|
||||
# Mail settings
|
||||
MAIL_DRIVER=smtp
|
||||
|
||||
21
.github/ISSUE_TEMPLATE.md
vendored
21
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,21 +0,0 @@
|
||||
### For Feature Requests
|
||||
|
||||
Desired Feature:
|
||||
|
||||
### For Bug Reports
|
||||
|
||||
* BookStack Version *(Found in settings, Please don't put 'latest')*:
|
||||
* PHP Version:
|
||||
* MySQL Version:
|
||||
|
||||
##### Expected Behavior
|
||||
|
||||
|
||||
|
||||
##### Current Behavior
|
||||
|
||||
|
||||
|
||||
##### Steps to Reproduce
|
||||
|
||||
|
||||
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Your Configuration (please complete the following information):**
|
||||
- Exact BookStack Version (Found in settings):
|
||||
- PHP Version:
|
||||
- Hosting Method (Nginx/Apache/Docker):
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Describe the feature you'd like**
|
||||
A clear description of the feature you'd like implemented in BookStack.
|
||||
|
||||
**Describe the benefits this feature would bring to BookStack users**
|
||||
Explain the measurable benefits this feature would achieve.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -31,6 +31,9 @@ class Attachment extends Ownable
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
if ($this->external && strpos($this->path, 'http') !== 0) {
|
||||
return $this->path;
|
||||
}
|
||||
return baseUrl('/attachments/' . $this->id);
|
||||
}
|
||||
}
|
||||
|
||||
83
app/Console/Commands/CleanupImages.php
Normal file
83
app/Console/Commands/CleanupImages.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Services\ImageService;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CleanupImages extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:cleanup-images
|
||||
{--a|all : Include images that are used in page revisions}
|
||||
{--f|force : Actually run the deletions}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Cleanup images and drawings';
|
||||
|
||||
|
||||
protected $imageService;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
* @param ImageService $imageService
|
||||
*/
|
||||
public function __construct(ImageService $imageService)
|
||||
{
|
||||
$this->imageService = $imageService;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$checkRevisions = $this->option('all') ? false : true;
|
||||
$dryRun = $this->option('force') ? false : true;
|
||||
|
||||
if (!$dryRun) {
|
||||
$proceed = $this->confirm("This operation is destructive and is not guaranteed to be fully accurate.\nEnsure you have a backup of your images.\nAre you sure you want to proceed?");
|
||||
if (!$proceed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$deleted = $this->imageService->deleteUnusedImages($checkRevisions, $dryRun);
|
||||
$deleteCount = count($deleted);
|
||||
|
||||
if ($dryRun) {
|
||||
$this->comment('Dry run, No images have been deleted');
|
||||
$this->comment($deleteCount . ' images found that would have been deleted');
|
||||
$this->showDeletedImages($deleted);
|
||||
$this->comment('Run with -f or --force to perform deletions');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->showDeletedImages($deleted);
|
||||
$this->comment($deleteCount . ' images deleted');
|
||||
}
|
||||
|
||||
protected function showDeletedImages($paths)
|
||||
{
|
||||
if ($this->getOutput()->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) return;
|
||||
if (count($paths) > 0) {
|
||||
$this->line('Images to delete:');
|
||||
}
|
||||
foreach ($paths as $path) {
|
||||
$this->line($path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ namespace BookStack\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pipeline\Pipeline;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
@@ -33,6 +31,7 @@ class Handler extends ExceptionHandler
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public function report(Exception $e)
|
||||
{
|
||||
@@ -65,30 +64,12 @@ class Handler extends ExceptionHandler
|
||||
|
||||
// Handle 404 errors with a loaded session to enable showing user-specific information
|
||||
if ($this->isExceptionType($e, NotFoundHttpException::class)) {
|
||||
return $this->loadErrorMiddleware($request, function ($request) use ($e) {
|
||||
$message = $e->getMessage() ?: trans('errors.404_page_not_found');
|
||||
return response()->view('errors/404', ['message' => $message], 404);
|
||||
});
|
||||
return \Route::respondWithRoute('fallback');
|
||||
}
|
||||
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the middleware required to show state/session-enabled error pages.
|
||||
* @param Request $request
|
||||
* @param $callback
|
||||
* @return mixed
|
||||
*/
|
||||
protected function loadErrorMiddleware(Request $request, $callback)
|
||||
{
|
||||
$middleware = (\Route::getMiddlewareGroups()['web_errors']);
|
||||
return (new Pipeline($this->container))
|
||||
->send($request)
|
||||
->through($middleware)
|
||||
->then($callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the exception chain to compare against the original exception type.
|
||||
* @param Exception $e
|
||||
|
||||
@@ -103,7 +103,7 @@ class AttachmentController extends Controller
|
||||
$this->validate($request, [
|
||||
'uploaded_to' => 'required|integer|exists:pages,id',
|
||||
'name' => 'required|string|min:1|max:255',
|
||||
'link' => 'url|min:1|max:255'
|
||||
'link' => 'string|min:1|max:255'
|
||||
]);
|
||||
|
||||
$pageId = $request->get('uploaded_to');
|
||||
@@ -131,7 +131,7 @@ class AttachmentController extends Controller
|
||||
$this->validate($request, [
|
||||
'uploaded_to' => 'required|integer|exists:pages,id',
|
||||
'name' => 'required|string|min:1|max:255',
|
||||
'link' => 'required|url|min:1|max:255'
|
||||
'link' => 'required|string|min:1|max:255'
|
||||
]);
|
||||
|
||||
$pageId = $request->get('uploaded_to');
|
||||
@@ -184,6 +184,7 @@ class AttachmentController extends Controller
|
||||
* @param $attachmentId
|
||||
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function get($attachmentId)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers\Auth;
|
||||
use BookStack\Exceptions\AuthException;
|
||||
use BookStack\Http\Controllers\Controller;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\LdapService;
|
||||
use BookStack\Services\SocialAuthService;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
@@ -36,18 +37,21 @@ class LoginController extends Controller
|
||||
protected $redirectAfterLogout = '/login';
|
||||
|
||||
protected $socialAuthService;
|
||||
protected $ldapService;
|
||||
protected $userRepo;
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @param SocialAuthService $socialAuthService
|
||||
* @param LdapService $ldapService
|
||||
* @param UserRepo $userRepo
|
||||
*/
|
||||
public function __construct(SocialAuthService $socialAuthService, UserRepo $userRepo)
|
||||
public function __construct(SocialAuthService $socialAuthService, LdapService $ldapService, UserRepo $userRepo)
|
||||
{
|
||||
$this->middleware('guest', ['only' => ['getLogin', 'postLogin']]);
|
||||
$this->socialAuthService = $socialAuthService;
|
||||
$this->ldapService = $ldapService;
|
||||
$this->userRepo = $userRepo;
|
||||
$this->redirectPath = baseUrl('/');
|
||||
$this->redirectAfterLogout = baseUrl('/login');
|
||||
@@ -66,6 +70,7 @@ class LoginController extends Controller
|
||||
* @param Authenticatable $user
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws AuthException
|
||||
* @throws \BookStack\Exceptions\LdapException
|
||||
*/
|
||||
protected function authenticated(Request $request, Authenticatable $user)
|
||||
{
|
||||
@@ -96,6 +101,11 @@ class LoginController extends Controller
|
||||
auth()->login($user);
|
||||
}
|
||||
|
||||
// Sync LDAP groups if required
|
||||
if ($this->ldapService->shouldSyncGroups()) {
|
||||
$this->ldapService->syncGroups($user, $request->get($this->username()));
|
||||
}
|
||||
|
||||
$path = session()->pull('url.intended', '/');
|
||||
$path = baseUrl($path, true);
|
||||
return redirect($path);
|
||||
@@ -125,6 +135,7 @@ class LoginController extends Controller
|
||||
* Redirect to the relevant social site.
|
||||
* @param $socialDriver
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* @throws \BookStack\Exceptions\SocialDriverNotConfigured
|
||||
*/
|
||||
public function getSocialLogin($socialDriver)
|
||||
{
|
||||
|
||||
@@ -33,22 +33,41 @@ class HomeController extends Controller
|
||||
$recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 12*$recentFactor);
|
||||
$recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdated('page', 12);
|
||||
|
||||
// Custom homepage
|
||||
|
||||
$customHomepage = false;
|
||||
$homepageSetting = setting('app-homepage');
|
||||
if ($homepageSetting) {
|
||||
$id = intval(explode(':', $homepageSetting)[0]);
|
||||
$customHomepage = $this->entityRepo->getById('page', $id, false, true);
|
||||
$this->entityRepo->renderPage($customHomepage, true);
|
||||
$books = false;
|
||||
$booksViewType = false;
|
||||
|
||||
// Check book homepage
|
||||
$bookHomepageSetting = setting('app-book-homepage');
|
||||
if ($bookHomepageSetting) {
|
||||
$books = $this->entityRepo->getAllPaginated('book', 18);
|
||||
$booksViewType = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books', 'list'));
|
||||
} else {
|
||||
// Check custom homepage
|
||||
$homepageSetting = setting('app-homepage');
|
||||
if ($homepageSetting) {
|
||||
$id = intval(explode(':', $homepageSetting)[0]);
|
||||
$customHomepage = $this->entityRepo->getById('page', $id, false, true);
|
||||
$this->entityRepo->renderPage($customHomepage, true);
|
||||
}
|
||||
}
|
||||
|
||||
$view = $customHomepage ? 'home-custom' : 'home';
|
||||
return view($view, [
|
||||
$view = 'home';
|
||||
if ($bookHomepageSetting) {
|
||||
$view = 'home-book';
|
||||
} else if ($customHomepage) {
|
||||
$view = 'home-custom';
|
||||
}
|
||||
|
||||
return view('common/' . $view, [
|
||||
'activity' => $activity,
|
||||
'recents' => $recents,
|
||||
'recentlyUpdatedPages' => $recentlyUpdatedPages,
|
||||
'draftPages' => $draftPages,
|
||||
'customHomepage' => $customHomepage
|
||||
'customHomepage' => $customHomepage,
|
||||
'books' => $books,
|
||||
'booksViewType' => $booksViewType
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -89,27 +108,6 @@ class HomeController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an icon via image request.
|
||||
* Can provide a 'color' parameter with hex value to color the icon.
|
||||
* @param $iconName
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function getIcon($iconName, Request $request)
|
||||
{
|
||||
$attrs = [];
|
||||
if ($request->filled('color')) {
|
||||
$attrs['fill'] = '#' . $request->get('color');
|
||||
}
|
||||
|
||||
$icon = icon($iconName, $attrs);
|
||||
return response($icon, 200, [
|
||||
'Content-Type' => 'image/svg+xml',
|
||||
'Cache-Control' => 'max-age=3600',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom head HTML, Used in ajax calls to show in editor.
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
@@ -131,7 +129,15 @@ class HomeController extends Controller
|
||||
$allowRobots = $sitePublic;
|
||||
}
|
||||
return response()
|
||||
->view('robots', ['allowRobots' => $allowRobots])
|
||||
->view('common/robots', ['allowRobots' => $allowRobots])
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the route for 404 responses.
|
||||
*/
|
||||
public function getNotFound()
|
||||
{
|
||||
return response()->view('errors/404', [], 404);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,32 +164,6 @@ class ImageController extends Controller
|
||||
return response()->json($image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the data content of a drawing.
|
||||
* @param string $id
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function replaceDrawing(string $id, Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'image' => 'required|string'
|
||||
]);
|
||||
$this->checkPermission('image-create-all');
|
||||
|
||||
$imageBase64Data = $request->get('image');
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$this->checkOwnablePermission('image-update', $image);
|
||||
|
||||
try {
|
||||
$image = $this->imageRepo->replaceDrawingContent($image, $imageBase64Data);
|
||||
} catch (ImageUploadException $e) {
|
||||
return response($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
return response()->json($image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of an image based64 encoded.
|
||||
* @param $id
|
||||
@@ -245,26 +219,29 @@ class ImageController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an image and all thumbnail/image files
|
||||
* Show the usage of an image on pages.
|
||||
* @param EntityRepo $entityRepo
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @param $id
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function destroy(EntityRepo $entityRepo, Request $request, $id)
|
||||
public function usage(EntityRepo $entityRepo, $id)
|
||||
{
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$pageSearch = $entityRepo->searchForImage($image->url);
|
||||
return response()->json($pageSearch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an image and all thumbnail/image files
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$image = $this->imageRepo->getById($id);
|
||||
$this->checkOwnablePermission('image-delete', $image);
|
||||
|
||||
// Check if this image is used on any pages
|
||||
$isForced = in_array($request->get('force', ''), [true, 'true']);
|
||||
if (!$isForced) {
|
||||
$pageSearch = $entityRepo->searchForImage($image->url);
|
||||
if ($pageSearch !== false) {
|
||||
return response()->json($pageSearch, 400);
|
||||
}
|
||||
}
|
||||
|
||||
$this->imageRepo->destroyImage($image);
|
||||
return response()->json(trans('components.images_deleted'));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Services\ExportService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Views;
|
||||
@@ -38,11 +37,18 @@ class PageController extends Controller
|
||||
* @param string $chapterSlug
|
||||
* @return Response
|
||||
* @internal param bool $pageSlug
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function create($bookSlug, $chapterSlug = null)
|
||||
{
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$chapter = $chapterSlug ? $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug) : null;
|
||||
if ($chapterSlug !== null) {
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$book = $chapter->book;
|
||||
} else {
|
||||
$chapter = null;
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
}
|
||||
|
||||
$parent = $chapter ? $chapter : $book;
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
@@ -52,7 +58,7 @@ class PageController extends Controller
|
||||
return redirect($draft->getUrl());
|
||||
}
|
||||
|
||||
// Otherwise show edit view
|
||||
// Otherwise show the edit view if they're a guest
|
||||
$this->setPageTitle(trans('entities.pages_new'));
|
||||
return view('pages/guest-create', ['parent' => $parent]);
|
||||
}
|
||||
@@ -71,8 +77,14 @@ class PageController extends Controller
|
||||
'name' => 'required|string|max:255'
|
||||
]);
|
||||
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$chapter = $chapterSlug ? $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug) : null;
|
||||
if ($chapterSlug !== null) {
|
||||
$chapter = $this->entityRepo->getBySlug('chapter', $chapterSlug, $bookSlug);
|
||||
$book = $chapter->book;
|
||||
} else {
|
||||
$chapter = null;
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
}
|
||||
|
||||
$parent = $chapter ? $chapter : $book;
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
@@ -93,7 +105,7 @@ class PageController extends Controller
|
||||
public function editDraft($bookSlug, $pageId)
|
||||
{
|
||||
$draft = $this->entityRepo->getById('page', $pageId, true);
|
||||
$this->checkOwnablePermission('page-create', $draft->book);
|
||||
$this->checkOwnablePermission('page-create', $draft->parent);
|
||||
$this->setPageTitle(trans('entities.pages_edit_draft'));
|
||||
|
||||
$draftsEnabled = $this->signedIn;
|
||||
@@ -119,12 +131,10 @@ class PageController extends Controller
|
||||
]);
|
||||
|
||||
$input = $request->all();
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
|
||||
$draftPage = $this->entityRepo->getById('page', $pageId, true);
|
||||
$book = $draftPage->book;
|
||||
|
||||
$chapterId = intval($draftPage->chapter_id);
|
||||
$parent = $chapterId !== 0 ? $this->entityRepo->getById('chapter', $chapterId) : $book;
|
||||
$parent = $draftPage->parent;
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
if ($parent->isA('chapter')) {
|
||||
|
||||
@@ -78,6 +78,7 @@ class PermissionController extends Controller
|
||||
* @param $id
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @throws PermissionsException
|
||||
*/
|
||||
public function updateRole($id, Request $request)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Services\ImageService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Setting;
|
||||
@@ -13,7 +14,7 @@ class SettingController extends Controller
|
||||
public function index()
|
||||
{
|
||||
$this->checkPermission('settings-manage');
|
||||
$this->setPageTitle('Settings');
|
||||
$this->setPageTitle(trans('settings.settings'));
|
||||
|
||||
// Get application version
|
||||
$version = trim(file_get_contents(base_path('version')));
|
||||
@@ -43,4 +44,48 @@ class SettingController extends Controller
|
||||
session()->flash('success', trans('settings.settings_save_success'));
|
||||
return redirect('/settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the page for application maintenance.
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function showMaintenance()
|
||||
{
|
||||
$this->checkPermission('settings-manage');
|
||||
$this->setPageTitle(trans('settings.maint'));
|
||||
|
||||
// Get application version
|
||||
$version = trim(file_get_contents(base_path('version')));
|
||||
|
||||
return view('settings/maintenance', ['version' => $version]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to clean-up images in the system.
|
||||
* @param Request $request
|
||||
* @param ImageService $imageService
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function cleanupImages(Request $request, ImageService $imageService)
|
||||
{
|
||||
$this->checkPermission('settings-manage');
|
||||
|
||||
$checkRevisions = !($request->get('ignore_revisions', 'false') === 'true');
|
||||
$dryRun = !($request->has('confirm'));
|
||||
|
||||
$imagesToDelete = $imageService->deleteUnusedImages($checkRevisions, $dryRun);
|
||||
$deleteCount = count($imagesToDelete);
|
||||
if ($deleteCount === 0) {
|
||||
session()->flash('warning', trans('settings.maint_image_cleanup_nothing_found'));
|
||||
return redirect('/settings/maintenance')->withInput();
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
session()->flash('cleanup-images-warning', trans('settings.maint_image_cleanup_warning', ['count' => $deleteCount]));
|
||||
} else {
|
||||
session()->flash('success', trans('settings.maint_image_cleanup_success', ['count' => $deleteCount]));
|
||||
}
|
||||
|
||||
return redirect('/settings/maintenance#image-cleanup')->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,6 @@ class Kernel extends HttpKernel
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\BookStack\Http\Middleware\Localization::class
|
||||
],
|
||||
'web_errors' => [
|
||||
\BookStack\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\BookStack\Http\Middleware\VerifyCsrfToken::class,
|
||||
\BookStack\Http\Middleware\Localization::class
|
||||
],
|
||||
'api' => [
|
||||
'throttle:60,1',
|
||||
'bindings',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Localization
|
||||
{
|
||||
@@ -15,21 +16,33 @@ class Localization
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$defaultLang = config('app.locale');
|
||||
if (user()->isDefault()) {
|
||||
$locale = $defaultLang;
|
||||
$availableLocales = config('app.locales');
|
||||
foreach ($request->getLanguages() as $lang) {
|
||||
if (!in_array($lang, $availableLocales)) {
|
||||
continue;
|
||||
}
|
||||
$locale = $lang;
|
||||
break;
|
||||
}
|
||||
|
||||
if (user()->isDefault() && config('app.auto_detect_locale')) {
|
||||
$locale = $this->autoDetectLocale($request, $defaultLang);
|
||||
} else {
|
||||
$locale = setting()->getUser(user(), 'language', $defaultLang);
|
||||
}
|
||||
|
||||
app()->setLocale($locale);
|
||||
Carbon::setLocale($locale);
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Autodetect the visitors locale by matching locales in their headers
|
||||
* against the locales supported by BookStack.
|
||||
* @param Request $request
|
||||
* @param string $default
|
||||
* @return string
|
||||
*/
|
||||
protected function autoDetectLocale(Request $request, string $default)
|
||||
{
|
||||
$availableLocales = config('app.locales');
|
||||
foreach ($request->getLanguages() as $lang) {
|
||||
if (in_array($lang, $availableLocales)) {
|
||||
return $lang;
|
||||
}
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,15 @@ class Image extends Ownable
|
||||
|
||||
/**
|
||||
* Get a thumbnail for this image.
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param bool|false $keepRatio
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getThumb($width, $height, $keepRatio = false)
|
||||
{
|
||||
return Images::getThumbnail($this, $width, $height, $keepRatio);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,15 @@ class Page extends Entity
|
||||
return $this->belongsTo(Book::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent item
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function parent()
|
||||
{
|
||||
return $this->chapter_id ? $this->chapter() : $this->book();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the chapter that this page is in, If applicable.
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace BookStack\Providers;
|
||||
|
||||
use BookStack\Activity;
|
||||
use BookStack\Image;
|
||||
use BookStack\Services\ImageService;
|
||||
use BookStack\Services\PermissionService;
|
||||
use BookStack\Services\ViewService;
|
||||
@@ -57,6 +58,7 @@ class CustomFacadeProvider extends ServiceProvider
|
||||
|
||||
$this->app->bind('images', function () {
|
||||
return new ImageService(
|
||||
$this->app->make(Image::class),
|
||||
$this->app->make(ImageManager::class),
|
||||
$this->app->make(Factory::class),
|
||||
$this->app->make(Repository::class)
|
||||
|
||||
@@ -20,6 +20,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
'SocialiteProviders\Okta\OktaExtendSocialite@handle',
|
||||
'SocialiteProviders\GitLab\GitLabExtendSocialite@handle',
|
||||
'SocialiteProviders\Twitch\TwitchExtendSocialite@handle',
|
||||
'SocialiteProviders\Discord\DiscordExtendSocialite@handle',
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -153,17 +153,6 @@ class ImageRepo
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the image content of a drawing.
|
||||
* @param Image $image
|
||||
* @param string $base64Uri
|
||||
* @return Image
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
*/
|
||||
public function replaceDrawingContent(Image $image, string $base64Uri)
|
||||
{
|
||||
return $this->imageService->replaceImageDataFromBase64Uri($image, $base64Uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the details of an image via an array of properties.
|
||||
@@ -183,13 +172,14 @@ class ImageRepo
|
||||
|
||||
|
||||
/**
|
||||
* Destroys an Image object along with its files and thumbnails.
|
||||
* Destroys an Image object along with its revisions, files and thumbnails.
|
||||
* @param Image $image
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function destroyImage(Image $image)
|
||||
{
|
||||
$this->imageService->destroyImage($image);
|
||||
$this->imageService->destroy($image);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -200,7 +190,7 @@ class ImageRepo
|
||||
* @throws \BookStack\Exceptions\ImageUploadException
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function loadThumbs(Image $image)
|
||||
protected function loadThumbs(Image $image)
|
||||
{
|
||||
$image->thumbs = [
|
||||
'gallery' => $this->getThumbnail($image, 150, 150),
|
||||
@@ -250,7 +240,7 @@ class ImageRepo
|
||||
*/
|
||||
public function isValidType($type)
|
||||
{
|
||||
$validTypes = ['drawing', 'gallery', 'cover', 'system', 'user'];
|
||||
$validTypes = ['gallery', 'cover', 'system', 'user'];
|
||||
return in_array($type, $validTypes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ class UserRepo
|
||||
// Delete user profile images
|
||||
$profileImages = $images = Image::where('type', '=', 'user')->where('created_by', '=', $user->id)->get();
|
||||
foreach ($profileImages as $image) {
|
||||
Images::destroyImage($image);
|
||||
Images::destroy($image);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
class Role extends Model
|
||||
{
|
||||
|
||||
protected $fillable = ['display_name', 'description'];
|
||||
protected $fillable = ['display_name', 'description', 'external_auth_id'];
|
||||
|
||||
/**
|
||||
* The roles that belong to the role.
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Image;
|
||||
use BookStack\User;
|
||||
use DB;
|
||||
use Exception;
|
||||
use Intervention\Image\Exception\NotSupportedException;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Illuminate\Contracts\Filesystem\Factory as FileSystem;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
@@ -17,15 +17,18 @@ class ImageService extends UploadService
|
||||
protected $imageTool;
|
||||
protected $cache;
|
||||
protected $storageUrl;
|
||||
protected $image;
|
||||
|
||||
/**
|
||||
* ImageService constructor.
|
||||
* @param $imageTool
|
||||
* @param $fileSystem
|
||||
* @param $cache
|
||||
* @param Image $image
|
||||
* @param ImageManager $imageTool
|
||||
* @param FileSystem $fileSystem
|
||||
* @param Cache $cache
|
||||
*/
|
||||
public function __construct(ImageManager $imageTool, FileSystem $fileSystem, Cache $cache)
|
||||
public function __construct(Image $image, ImageManager $imageTool, FileSystem $fileSystem, Cache $cache)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->imageTool = $imageTool;
|
||||
$this->cache = $cache;
|
||||
parent::__construct($fileSystem);
|
||||
@@ -82,31 +85,6 @@ class ImageService extends UploadService
|
||||
return $this->saveNew($name, $data, $type, $uploadedTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the data for an image via a Base64 encoded string.
|
||||
* @param Image $image
|
||||
* @param string $base64Uri
|
||||
* @return Image
|
||||
* @throws ImageUploadException
|
||||
*/
|
||||
public function replaceImageDataFromBase64Uri(Image $image, string $base64Uri)
|
||||
{
|
||||
$splitData = explode(';base64,', $base64Uri);
|
||||
if (count($splitData) < 2) {
|
||||
throw new ImageUploadException("Invalid base64 image data provided");
|
||||
}
|
||||
$data = base64_decode($splitData[1]);
|
||||
$storage = $this->getStorage();
|
||||
|
||||
try {
|
||||
$storage->put($image->path, $data);
|
||||
} catch (Exception $e) {
|
||||
throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $image->path]));
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an image from url and saves it to the database.
|
||||
* @param $url
|
||||
@@ -140,16 +118,16 @@ class ImageService extends UploadService
|
||||
$secureUploads = setting('app-secure-images');
|
||||
$imageName = str_replace(' ', '-', $imageName);
|
||||
|
||||
if ($secureUploads) {
|
||||
$imageName = str_random(16) . '-' . $imageName;
|
||||
}
|
||||
|
||||
$imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/';
|
||||
|
||||
while ($storage->exists($imagePath . $imageName)) {
|
||||
$imageName = str_random(3) . $imageName;
|
||||
}
|
||||
|
||||
$fullPath = $imagePath . $imageName;
|
||||
if ($secureUploads) {
|
||||
$fullPath = $imagePath . str_random(16) . '-' . $imageName;
|
||||
}
|
||||
|
||||
try {
|
||||
$storage->put($fullPath, $imageData);
|
||||
@@ -172,20 +150,11 @@ class ImageService extends UploadService
|
||||
$imageDetails['updated_by'] = $userId;
|
||||
}
|
||||
|
||||
$image = (new Image());
|
||||
$image = $this->image->newInstance();
|
||||
$image->forceFill($imageDetails)->save();
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the storage path, Dependant of storage type.
|
||||
* @param Image $image
|
||||
* @return mixed|string
|
||||
*/
|
||||
protected function getPath(Image $image)
|
||||
{
|
||||
return $image->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the image is a gif. Returns true if it is, else false.
|
||||
@@ -194,7 +163,7 @@ class ImageService extends UploadService
|
||||
*/
|
||||
protected function isGif(Image $image)
|
||||
{
|
||||
return strtolower(pathinfo($this->getPath($image), PATHINFO_EXTENSION)) === 'gif';
|
||||
return strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) === 'gif';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,11 +181,11 @@ class ImageService extends UploadService
|
||||
public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
|
||||
{
|
||||
if ($keepRatio && $this->isGif($image)) {
|
||||
return $this->getPublicUrl($this->getPath($image));
|
||||
return $this->getPublicUrl($image->path);
|
||||
}
|
||||
|
||||
$thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/';
|
||||
$imagePath = $this->getPath($image);
|
||||
$imagePath = $image->path;
|
||||
$thumbFilePath = dirname($imagePath) . $thumbDirName . basename($imagePath);
|
||||
|
||||
if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) {
|
||||
@@ -262,43 +231,51 @@ class ImageService extends UploadService
|
||||
*/
|
||||
public function getImageData(Image $image)
|
||||
{
|
||||
$imagePath = $this->getPath($image);
|
||||
$imagePath = $image->path;
|
||||
$storage = $this->getStorage();
|
||||
return $storage->get($imagePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys an Image object along with its files and thumbnails.
|
||||
* Destroy an image along with its revisions, thumbnails and remaining folders.
|
||||
* @param Image $image
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function destroyImage(Image $image)
|
||||
public function destroy(Image $image)
|
||||
{
|
||||
$this->destroyImagesFromPath($image->path);
|
||||
$image->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys an image at the given path.
|
||||
* Searches for image thumbnails in addition to main provided path..
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
protected function destroyImagesFromPath(string $path)
|
||||
{
|
||||
$storage = $this->getStorage();
|
||||
|
||||
$imageFolder = dirname($this->getPath($image));
|
||||
$imageFileName = basename($this->getPath($image));
|
||||
$imageFolder = dirname($path);
|
||||
$imageFileName = basename($path);
|
||||
$allImages = collect($storage->allFiles($imageFolder));
|
||||
|
||||
// Delete image files
|
||||
$imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) {
|
||||
$expectedIndex = strlen($imagePath) - strlen($imageFileName);
|
||||
return strpos($imagePath, $imageFileName) === $expectedIndex;
|
||||
});
|
||||
|
||||
$storage->delete($imagesToDelete->all());
|
||||
|
||||
// Cleanup of empty folders
|
||||
foreach ($storage->directories($imageFolder) as $directory) {
|
||||
$foldersInvolved = array_merge([$imageFolder], $storage->directories($imageFolder));
|
||||
foreach ($foldersInvolved as $directory) {
|
||||
if ($this->isFolderEmpty($directory)) {
|
||||
$storage->deleteDirectory($directory);
|
||||
}
|
||||
}
|
||||
if ($this->isFolderEmpty($imageFolder)) {
|
||||
$storage->deleteDirectory($imageFolder);
|
||||
}
|
||||
|
||||
$image->delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -321,6 +298,46 @@ class ImageService extends UploadService
|
||||
return $image;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete gallery and drawings that are not within HTML content of pages or page revisions.
|
||||
* Checks based off of only the image name.
|
||||
* Could be much improved to be more specific but kept it generic for now to be safe.
|
||||
*
|
||||
* Returns the path of the images that would be/have been deleted.
|
||||
* @param bool $checkRevisions
|
||||
* @param bool $dryRun
|
||||
* @param array $types
|
||||
* @return array
|
||||
*/
|
||||
public function deleteUnusedImages($checkRevisions = true, $dryRun = true, $types = ['gallery', 'drawio'])
|
||||
{
|
||||
$types = array_intersect($types, ['gallery', 'drawio']);
|
||||
$deletedPaths = [];
|
||||
|
||||
$this->image->newQuery()->whereIn('type', $types)
|
||||
->chunk(1000, function($images) use ($types, $checkRevisions, &$deletedPaths, $dryRun) {
|
||||
foreach ($images as $image) {
|
||||
$searchQuery = '%' . basename($image->path) . '%';
|
||||
$inPage = DB::table('pages')
|
||||
->where('html', 'like', $searchQuery)->count() > 0;
|
||||
$inRevision = false;
|
||||
if ($checkRevisions) {
|
||||
$inRevision = DB::table('page_revisions')
|
||||
->where('html', 'like', $searchQuery)->count() > 0;
|
||||
}
|
||||
|
||||
if (!$inPage && !$inRevision) {
|
||||
$deletedPaths[] = $image->path;
|
||||
if (!$dryRun) {
|
||||
$this->destroy($image);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return $deletedPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a image URI to a Base64 encoded string.
|
||||
* Attempts to find locally via set storage method first.
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<?php namespace BookStack\Services;
|
||||
|
||||
use BookStack\Exceptions\LdapException;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use BookStack\Role;
|
||||
use BookStack\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* Class LdapService
|
||||
@@ -14,15 +18,55 @@ class LdapService
|
||||
protected $ldap;
|
||||
protected $ldapConnection;
|
||||
protected $config;
|
||||
protected $userRepo;
|
||||
protected $enabled;
|
||||
|
||||
/**
|
||||
* LdapService constructor.
|
||||
* @param Ldap $ldap
|
||||
* @param UserRepo $userRepo
|
||||
*/
|
||||
public function __construct(Ldap $ldap)
|
||||
public function __construct(Ldap $ldap, UserRepo $userRepo)
|
||||
{
|
||||
$this->ldap = $ldap;
|
||||
$this->config = config('services.ldap');
|
||||
$this->userRepo = $userRepo;
|
||||
$this->enabled = config('auth.method') === 'ldap';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if groups should be synced.
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldSyncGroups()
|
||||
{
|
||||
return $this->enabled && $this->config['user_to_groups'] !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for attributes for a specific user on the ldap
|
||||
* @param string $userName
|
||||
* @param array $attributes
|
||||
* @return null|array
|
||||
* @throws LdapException
|
||||
*/
|
||||
private function getUserWithAttributes($userName, $attributes)
|
||||
{
|
||||
$ldapConnection = $this->getConnection();
|
||||
$this->bindSystemUser($ldapConnection);
|
||||
|
||||
// Find user
|
||||
$userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
|
||||
$baseDn = $this->config['base_dn'];
|
||||
|
||||
$followReferrals = $this->config['follow_referrals'] ? 1 : 0;
|
||||
$this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
|
||||
$users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, $attributes);
|
||||
if ($users['count'] === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $users[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,21 +78,13 @@ class LdapService
|
||||
*/
|
||||
public function getUserDetails($userName)
|
||||
{
|
||||
$ldapConnection = $this->getConnection();
|
||||
$this->bindSystemUser($ldapConnection);
|
||||
|
||||
// Find user
|
||||
$userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
|
||||
$baseDn = $this->config['base_dn'];
|
||||
$emailAttr = $this->config['email_attribute'];
|
||||
$followReferrals = $this->config['follow_referrals'] ? 1 : 0;
|
||||
$this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
|
||||
$users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', $emailAttr]);
|
||||
if ($users['count'] === 0) {
|
||||
$user = $this->getUserWithAttributes($userName, ['cn', 'uid', 'dn', $emailAttr]);
|
||||
|
||||
if ($user === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = $users[0];
|
||||
return [
|
||||
'uid' => (isset($user['uid'])) ? $user['uid'][0] : $user['dn'],
|
||||
'name' => $user['cn'][0],
|
||||
@@ -162,4 +198,173 @@ class LdapService
|
||||
}
|
||||
return strtr($filterString, $newAttrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the groups a user is a part of on ldap
|
||||
* @param string $userName
|
||||
* @return array
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function getUserGroups($userName)
|
||||
{
|
||||
$groupsAttr = $this->config['group_attribute'];
|
||||
$user = $this->getUserWithAttributes($userName, [$groupsAttr]);
|
||||
|
||||
if ($user === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$userGroups = $this->groupFilter($user);
|
||||
$userGroups = $this->getGroupsRecursive($userGroups, []);
|
||||
return $userGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent groups of an array of groups
|
||||
* @param array $groupsArray
|
||||
* @param array $checked
|
||||
* @return array
|
||||
* @throws LdapException
|
||||
*/
|
||||
private function getGroupsRecursive($groupsArray, $checked)
|
||||
{
|
||||
$groups_to_add = [];
|
||||
foreach ($groupsArray as $groupName) {
|
||||
if (in_array($groupName, $checked)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$groupsToAdd = $this->getGroupGroups($groupName);
|
||||
$groups_to_add = array_merge($groups_to_add, $groupsToAdd);
|
||||
$checked[] = $groupName;
|
||||
}
|
||||
$groupsArray = array_unique(array_merge($groupsArray, $groups_to_add), SORT_REGULAR);
|
||||
|
||||
if (!empty($groups_to_add)) {
|
||||
return $this->getGroupsRecursive($groupsArray, $checked);
|
||||
} else {
|
||||
return $groupsArray;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent groups of a single group
|
||||
* @param string $groupName
|
||||
* @return array
|
||||
* @throws LdapException
|
||||
*/
|
||||
private function getGroupGroups($groupName)
|
||||
{
|
||||
$ldapConnection = $this->getConnection();
|
||||
$this->bindSystemUser($ldapConnection);
|
||||
|
||||
$followReferrals = $this->config['follow_referrals'] ? 1 : 0;
|
||||
$this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
|
||||
|
||||
$baseDn = $this->config['base_dn'];
|
||||
$groupsAttr = strtolower($this->config['group_attribute']);
|
||||
|
||||
$groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, 'CN='.$groupName, [$groupsAttr]);
|
||||
if ($groups['count'] === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$groupGroups = $this->groupFilter($groups[0]);
|
||||
return $groupGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out LDAP CN and DN language in a ldap search return
|
||||
* Gets the base CN (common name) of the string
|
||||
* @param string $ldapSearchReturn
|
||||
* @return array
|
||||
*/
|
||||
protected function groupFilter($ldapSearchReturn)
|
||||
{
|
||||
$groupsAttr = strtolower($this->config['group_attribute']);
|
||||
$ldapGroups = [];
|
||||
$count = 0;
|
||||
if (isset($ldapSearchReturn[$groupsAttr]['count'])) {
|
||||
$count = (int) $ldapSearchReturn[$groupsAttr]['count'];
|
||||
}
|
||||
for ($i=0; $i<$count; $i++) {
|
||||
$dnComponents = ldap_explode_dn($ldapSearchReturn[$groupsAttr][$i], 1);
|
||||
if (!in_array($dnComponents[0], $ldapGroups)) {
|
||||
$ldapGroups[] = $dnComponents[0];
|
||||
}
|
||||
}
|
||||
return $ldapGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the LDAP groups to the user roles for the current user
|
||||
* @param \BookStack\User $user
|
||||
* @param string $username
|
||||
* @throws LdapException
|
||||
*/
|
||||
public function syncGroups(User $user, string $username)
|
||||
{
|
||||
$userLdapGroups = $this->getUserGroups($username);
|
||||
|
||||
// Get the ids for the roles from the names
|
||||
$ldapGroupsAsRoles = $this->matchLdapGroupsToSystemsRoles($userLdapGroups);
|
||||
|
||||
// Sync groups
|
||||
if ($this->config['remove_from_groups']) {
|
||||
$user->roles()->sync($ldapGroupsAsRoles);
|
||||
$this->userRepo->attachDefaultRole($user);
|
||||
} else {
|
||||
$user->roles()->syncWithoutDetaching($ldapGroupsAsRoles);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match an array of group names from LDAP to BookStack system roles.
|
||||
* Formats LDAP group names to be lower-case and hyphenated.
|
||||
* @param array $groupNames
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function matchLdapGroupsToSystemsRoles(array $groupNames)
|
||||
{
|
||||
foreach ($groupNames as $i => $groupName) {
|
||||
$groupNames[$i] = str_replace(' ', '-', trim(strtolower($groupName)));
|
||||
}
|
||||
|
||||
$roles = Role::query()->where(function(Builder $query) use ($groupNames) {
|
||||
$query->whereIn('name', $groupNames);
|
||||
foreach ($groupNames as $groupName) {
|
||||
$query->orWhere('external_auth_id', 'LIKE', '%' . $groupName . '%');
|
||||
}
|
||||
})->get();
|
||||
|
||||
$matchedRoles = $roles->filter(function(Role $role) use ($groupNames) {
|
||||
return $this->roleMatchesGroupNames($role, $groupNames);
|
||||
});
|
||||
|
||||
return $matchedRoles->pluck('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a role against an array of group names to see if it matches.
|
||||
* Checked against role 'external_auth_id' if set otherwise the name of the role.
|
||||
* @param Role $role
|
||||
* @param array $groupNames
|
||||
* @return bool
|
||||
*/
|
||||
protected function roleMatchesGroupNames(Role $role, array $groupNames)
|
||||
{
|
||||
if ($role->external_auth_id) {
|
||||
$externalAuthIds = explode(',', strtolower($role->external_auth_id));
|
||||
foreach ($externalAuthIds as $externalAuthId) {
|
||||
if (in_array(trim($externalAuthId), $groupNames)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$roleName = str_replace(' ', '-', trim(strtolower($role->display_name)));
|
||||
return in_array($roleName, $groupNames);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class SocialAuthService
|
||||
protected $socialite;
|
||||
protected $socialAccount;
|
||||
|
||||
protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter', 'azure', 'okta', 'gitlab', 'twitch'];
|
||||
protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter', 'azure', 'okta', 'gitlab', 'twitch', 'discord'];
|
||||
|
||||
/**
|
||||
* SocialAuthService constructor.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"laravel/framework": "5.5.*",
|
||||
"laravel/framework": "~5.5.42",
|
||||
"fideloper/proxy": "~3.3",
|
||||
"ext-tidy": "*",
|
||||
"intervention/image": "^2.4",
|
||||
@@ -20,7 +20,8 @@
|
||||
"socialiteproviders/microsoft-azure": "^3.0",
|
||||
"socialiteproviders/okta": "^1.0",
|
||||
"socialiteproviders/gitlab": "^3.0",
|
||||
"socialiteproviders/twitch": "^3.0"
|
||||
"socialiteproviders/twitch": "^3.0",
|
||||
"socialiteproviders/discord": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"filp/whoops": "~2.0",
|
||||
|
||||
472
composer.lock
generated
472
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -77,8 +77,21 @@ return [
|
||||
*/
|
||||
|
||||
'locale' => env('APP_LANG', 'en'),
|
||||
|
||||
'locales' => ['en', 'de', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'ja', 'pl', 'it', 'ru', 'zh_CN', 'zh_TW'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Auto-detect the locale for public users
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| For public users their locale can be guessed by headers sent by their
|
||||
| browser. This is usually set by users in their browser settings.
|
||||
| If not found the default app locale will be used.
|
||||
|
|
||||
*/
|
||||
'auto_detect_locale' => env('APP_AUTO_LANG_PUBLIC', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Fallback Locale
|
||||
@@ -245,7 +258,7 @@ return [
|
||||
'Activity' => BookStack\Services\Facades\Activity::class,
|
||||
'Setting' => BookStack\Services\Facades\Setting::class,
|
||||
'Views' => BookStack\Services\Facades\Views::class,
|
||||
'Images' => \BookStack\Services\Facades\Images::class,
|
||||
'Images' => BookStack\Services\Facades\Images::class,
|
||||
|
||||
],
|
||||
|
||||
|
||||
@@ -108,6 +108,12 @@ return [
|
||||
'redirect' => env('APP_URL') . '/login/service/twitch/callback',
|
||||
'name' => 'Twitch',
|
||||
],
|
||||
'discord' => [
|
||||
'client_id' => env('DISCORD_APP_ID'),
|
||||
'client_secret' => env('DISCORD_APP_SECRET'),
|
||||
'redirect' => env('APP_URL') . '/login/service/discord/callback',
|
||||
'name' => 'Discord',
|
||||
],
|
||||
|
||||
'ldap' => [
|
||||
'server' => env('LDAP_SERVER', false),
|
||||
@@ -118,6 +124,9 @@ return [
|
||||
'version' => env('LDAP_VERSION', false),
|
||||
'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
|
||||
'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
|
||||
]
|
||||
'user_to_groups' => env('LDAP_USER_TO_GROUPS',false),
|
||||
'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'),
|
||||
'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS',false),
|
||||
]
|
||||
|
||||
];
|
||||
|
||||
@@ -135,7 +135,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => null,
|
||||
'domain' => env('SESSION_DOMAIN', null),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -148,6 +148,34 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'secure' => false,
|
||||
'secure' => env('SESSION_SECURE_COOKIE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTP Access Only
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will prevent JavaScript from accessing the
|
||||
| value of the cookie and the cookie will only be accessible through
|
||||
| the HTTP protocol. You are free to modify this option if needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'http_only' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Same-Site Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines how your cookies behave when cross-site requests
|
||||
| take place, and can be used to mitigate CSRF attacks. By default, we
|
||||
| do not enable this as other CSRF protection services are in place.
|
||||
|
|
||||
| Supported: "lax", "strict"
|
||||
|
|
||||
*/
|
||||
|
||||
'same_site' => null,
|
||||
|
||||
];
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddRoleExternalAuthId extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->string('external_auth_id', 200)->default('');
|
||||
$table->index('external_auth_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('roles', function (Blueprint $table) {
|
||||
$table->dropColumn('external_auth_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
6361
package-lock.json
generated
6361
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -11,20 +11,20 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0-beta.40",
|
||||
"@babel/polyfill": "^7.0.0-beta.40",
|
||||
"@babel/preset-env": "^7.0.0-beta.40",
|
||||
"autoprefixer": "^8.1.0",
|
||||
"babel-loader": "^8.0.0-beta.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"css-loader": "^0.28.10",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"livereload": "^0.7.0",
|
||||
"node-sass": "^4.7.2",
|
||||
"node-sass": "^4.9.2",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"postcss-loader": "^2.1.1",
|
||||
"sass-loader": "^6.0.7",
|
||||
"style-loader": "^0.20.3",
|
||||
"sass-loader": "^7.0.1",
|
||||
"style-loader": "^0.21.0",
|
||||
"uglifyjs-webpack-plugin": "^1.2.3",
|
||||
"webpack": "^4.1.1",
|
||||
"webpack": "^4.16.3",
|
||||
"webpack-cli": "^2.0.11"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="APP_DEBUG" value="false"/>
|
||||
<env name="APP_LANG" value="en"/>
|
||||
<env name="APP_AUTO_LANG_PUBLIC" value="true"/>
|
||||
<env name="CACHE_DRIVER" value="array"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
<env name="QUEUE_DRIVER" value="sync"/>
|
||||
|
||||
2
public/dist/app.js
vendored
2
public/dist/app.js
vendored
File diff suppressed because one or more lines are too long
119
public/dist/export-styles.css
vendored
119
public/dist/export-styles.css
vendored
@@ -107,6 +107,11 @@ a, .link {
|
||||
position: relative;
|
||||
display: inline-block; }
|
||||
|
||||
.blended-links a {
|
||||
color: inherit; }
|
||||
.blended-links a svg {
|
||||
fill: currentColor; }
|
||||
|
||||
/*
|
||||
* Other HTML Text Elements
|
||||
*/
|
||||
@@ -421,6 +426,8 @@ body.flexbox {
|
||||
background-color: #F2F2F2;
|
||||
max-width: 360px;
|
||||
min-height: 90vh; }
|
||||
.flex.sidebar section {
|
||||
margin: 16px; }
|
||||
|
||||
.flex.sidebar + .flex.content {
|
||||
flex: 3;
|
||||
@@ -1177,7 +1184,7 @@ div[class^="col-"] img {
|
||||
display: block;
|
||||
position: relative; }
|
||||
.callout:before {
|
||||
background-image: url("/icon/info-filled.svg?color=015380");
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMwMTUzODAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTEgMTVoLTJ2LTZoMnY2em0wLThoLTJWN2gydjJ6Ii8+PC9zdmc+");
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
width: 1.2em;
|
||||
@@ -1194,13 +1201,13 @@ div[class^="col-"] img {
|
||||
background-color: #e7f3e7;
|
||||
color: #376c39; }
|
||||
.callout.success:before {
|
||||
background-image: url("/icon/check-circle.svg?color=376c39"); }
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMzNzZjMzkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bS0yIDE1bC01LTUgMS40MS0xLjQxTDEwIDE0LjE3bDcuNTktNy41OUwxOSA4bC05IDl6Ii8+PC9zdmc+"); }
|
||||
.callout.danger {
|
||||
border-left-color: #E84F4F;
|
||||
background-color: #fce8e8;
|
||||
color: #b91818; }
|
||||
.callout.danger:before {
|
||||
background-image: url("/icon/danger.svg?color=b91818"); }
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiOTE4MTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0xNS43MyAzSDguMjdMMyA4LjI3djcuNDZMOC4yNyAyMWg3LjQ2TDIxIDE1LjczVjguMjdMMTUuNzMgM3pNMTIgMTcuM2MtLjcyIDAtMS4zLS41OC0xLjMtMS4zIDAtLjcyLjU4LTEuMyAxLjMtMS4zLjcyIDAgMS4zLjU4IDEuMyAxLjMgMCAuNzItLjU4IDEuMy0xLjMgMS4zem0xLTQuM2gtMlY3aDJ2NnoiLz4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg=="); }
|
||||
.callout.info {
|
||||
border-left-color: #0288D1;
|
||||
background-color: #d3efff;
|
||||
@@ -1210,7 +1217,7 @@ div[class^="col-"] img {
|
||||
background-color: #faeae0;
|
||||
color: #b6531c; }
|
||||
.callout.warning:before {
|
||||
background-image: url("/icon/warning.svg?color=b6531c"); }
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiNjUzMWMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEgMjFoMjJMMTIgMiAxIDIxem0xMi0zaC0ydi0yaDJ2MnptMC00aC0ydi00aDJ2NHoiLz48L3N2Zz4="); }
|
||||
|
||||
.card {
|
||||
margin: 16px;
|
||||
@@ -1233,6 +1240,9 @@ div[class^="col-"] img {
|
||||
word-wrap: break-word;
|
||||
word-break: break-word; }
|
||||
|
||||
.sidebar .card h3, .sidebar .card .body, .sidebar .card .empty-text {
|
||||
padding: 12px 16px; }
|
||||
|
||||
.card.drag-card {
|
||||
border: 1px solid #DDD;
|
||||
border-radius: 4px;
|
||||
@@ -1278,6 +1288,30 @@ div[class^="col-"] img {
|
||||
padding: 16px;
|
||||
border: 1px solid #DDD; }
|
||||
|
||||
.tag-item {
|
||||
display: inline-flex;
|
||||
margin-bottom: 6px;
|
||||
margin-right: 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #CCC;
|
||||
overflow: hidden;
|
||||
font-size: 0.85em; }
|
||||
.tag-item a, .tag-item a:hover, .tag-item a:active {
|
||||
padding: 4px 8px;
|
||||
color: #777;
|
||||
transition: background-color ease-in-out 80ms;
|
||||
text-decoration: none; }
|
||||
.tag-item a:hover {
|
||||
background-color: rgba(255, 255, 255, 0.7); }
|
||||
.tag-item svg {
|
||||
fill: #888; }
|
||||
.tag-item .tag-value {
|
||||
border-left: 1px solid #DDD;
|
||||
background-color: rgba(255, 255, 255, 0.5); }
|
||||
|
||||
.tag-list div:last-child .tag-item {
|
||||
margin-bottom: 0; }
|
||||
|
||||
.input-base, .fake-input, input[type="text"], input[type="number"], input[type="email"], input[type="date"], input[type="search"], input[type="url"], input[type="password"], select, textarea {
|
||||
background-color: #FFF;
|
||||
border-radius: 3px;
|
||||
@@ -1292,9 +1326,9 @@ div[class^="col-"] img {
|
||||
border: 1px solid #E84F4F; }
|
||||
.input-base.pos, .pos.fake-input, input.pos[type="text"], input.pos[type="number"], input.pos[type="email"], input.pos[type="date"], input.pos[type="search"], input.pos[type="url"], input.pos[type="password"], select.pos, textarea.pos, .input-base.valid, .valid.fake-input, input.valid[type="text"], input.valid[type="number"], input.valid[type="email"], input.valid[type="date"], input.valid[type="search"], input.valid[type="url"], input.valid[type="password"], select.valid, textarea.valid {
|
||||
border: 1px solid #52A256; }
|
||||
.input-base.disabled, .disabled.fake-input, input.disabled[type="text"], input.disabled[type="number"], input.disabled[type="email"], input.disabled[type="date"], input.disabled[type="search"], input.disabled[type="url"], input.disabled[type="password"], select.disabled, textarea.disabled, .input-base[disabled], [disabled].fake-input, input[disabled][type="text"], input[disabled][type="number"], input[disabled][type="email"], input[disabled][type="date"], input[disabled][type="search"], input[disabled][type="url"], input[disabled][type="password"], select[disabled], textarea[disabled] {
|
||||
.input-base.disabled, .disabled.fake-input, input.disabled[type="text"], input.disabled[type="number"], input.disabled[type="email"], input.disabled[type="date"], input.disabled[type="search"], input.disabled[type="url"], input.disabled[type="password"], select.disabled, textarea.disabled, .input-base[disabled], .fake-input[disabled], input[disabled][type="text"], input[disabled][type="number"], input[disabled][type="email"], input[disabled][type="date"], input[disabled][type="search"], input[disabled][type="url"], input[disabled][type="password"], select[disabled], textarea[disabled] {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAMUlEQVQIW2NkwAGuXbv2nxGbHEhCS0uLEUMSJgHShCKJLIEiiS4Bl8QmAZbEJQGSBAC62BuJ+tt7zgAAAABJRU5ErkJggg==); }
|
||||
.input-base:focus, .fake-input:focus, input[type="text"]:focus, input[type="number"]:focus, input[type="email"]:focus, input[type="date"]:focus, input[type="search"]:focus, input[type="url"]:focus, input[type="password"]:focus, select:focus, textarea:focus {
|
||||
.input-base:focus, .fake-input:focus, input:focus[type="text"], input:focus[type="number"], input:focus[type="email"], input:focus[type="date"], input:focus[type="search"], input:focus[type="url"], input:focus[type="password"], select:focus, textarea:focus {
|
||||
outline: 0; }
|
||||
|
||||
.fake-input {
|
||||
@@ -1607,20 +1641,21 @@ header {
|
||||
header .links {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-right: 32px; }
|
||||
@media screen and (max-width: 992px) {
|
||||
header .links {
|
||||
margin-right: 16px; } }
|
||||
margin-left: 16px; }
|
||||
header .links a {
|
||||
display: inline-block;
|
||||
padding: 16px 24px;
|
||||
padding: 16px;
|
||||
color: #FFF;
|
||||
fill: #FFF; }
|
||||
header .links a:last-child {
|
||||
padding-right: 0; }
|
||||
@media screen and (max-width: 992px) {
|
||||
header .links a {
|
||||
padding: 16px 12px; } }
|
||||
header .dropdown-container {
|
||||
padding-left: 16px;
|
||||
padding-right: 0; }
|
||||
@media screen and (max-width: 992px) {
|
||||
header .links a {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px; }
|
||||
header .dropdown-container {
|
||||
padding-left: 12px; } }
|
||||
header .avatar, header .user-name {
|
||||
display: inline-block; }
|
||||
header .avatar {
|
||||
@@ -1666,10 +1701,13 @@ header .search-box {
|
||||
header .search-box input {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: #EEE; }
|
||||
header .search-box button {
|
||||
color: #EEE;
|
||||
fill: #EEE; }
|
||||
z-index: 2; }
|
||||
header .search-box button {
|
||||
fill: #EEE;
|
||||
z-index: 1; }
|
||||
header .search-box button svg {
|
||||
margin-right: 0; }
|
||||
header .search-box ::-webkit-input-placeholder {
|
||||
/* Chrome/Opera/Safari */
|
||||
color: #DDD; }
|
||||
@@ -2004,8 +2042,6 @@ ul.pagination {
|
||||
padding: 3px 12px;
|
||||
border: 1px solid #CCC;
|
||||
margin-left: -1px;
|
||||
color: #888;
|
||||
fill: #888;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
@@ -2013,13 +2049,7 @@ ul.pagination {
|
||||
ul.pagination a.disabled, ul.pagination span.disabled {
|
||||
cursor: not-allowed; }
|
||||
ul.pagination li.active span {
|
||||
background-color: rgba(2, 136, 209, 0.8);
|
||||
color: #EEE;
|
||||
fill: #EEE;
|
||||
border-color: rgba(2, 136, 209, 0.8); }
|
||||
ul.pagination a {
|
||||
color: #0288D1;
|
||||
fill: #0288D1; }
|
||||
color: #FFF; }
|
||||
|
||||
.compact ul.pagination {
|
||||
margin: 0; }
|
||||
@@ -2183,10 +2213,13 @@ ul.pagination {
|
||||
padding: 0 !important; }
|
||||
|
||||
.page-content {
|
||||
width: 100%;
|
||||
max-width: 840px;
|
||||
margin: 0 auto;
|
||||
margin-top: 48px;
|
||||
overflow-wrap: break-word; }
|
||||
.page-content.flex {
|
||||
margin-top: 16px; }
|
||||
.page-content .align-left {
|
||||
text-align: left; }
|
||||
.page-content img.align-left, .page-content table.align-left {
|
||||
@@ -2221,6 +2254,8 @@ ul.pagination {
|
||||
background: #dbffdb; }
|
||||
.page-content del {
|
||||
background: #FFECEC; }
|
||||
.page-content.page-revision pre code {
|
||||
white-space: pre-wrap; }
|
||||
|
||||
.pointer-container {
|
||||
position: relative;
|
||||
@@ -2237,8 +2272,10 @@ ul.pagination {
|
||||
position: absolute;
|
||||
top: -60px;
|
||||
background-color: #FFF;
|
||||
width: 272px;
|
||||
width: 275px;
|
||||
z-index: 55; }
|
||||
.pointer.is-page-editable {
|
||||
width: 328px; }
|
||||
.pointer:before {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
@@ -2262,12 +2299,13 @@ ul.pagination {
|
||||
color: #666;
|
||||
width: 172px;
|
||||
z-index: 40; }
|
||||
.pointer input, .pointer button {
|
||||
.pointer input, .pointer button, .pointer a {
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
height: 28px;
|
||||
font-size: 12px;
|
||||
vertical-align: top; }
|
||||
vertical-align: top;
|
||||
padding: 5px 16px; }
|
||||
.pointer > i {
|
||||
color: #888;
|
||||
font-size: 18px;
|
||||
@@ -2278,10 +2316,17 @@ ul.pagination {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none; }
|
||||
.pointer .button {
|
||||
.pointer .input-group .button {
|
||||
line-height: 1;
|
||||
margin: 0 0 0 -4px;
|
||||
box-shadow: none; }
|
||||
.pointer a.button {
|
||||
margin: 0 0 0 0; }
|
||||
.pointer a.button:hover {
|
||||
fill: #fff; }
|
||||
.pointer .svg-icon {
|
||||
width: 1.2em;
|
||||
height: 1.2em; }
|
||||
|
||||
.floating-toolbox {
|
||||
background-color: #FFF;
|
||||
@@ -2418,6 +2463,16 @@ ul.pagination {
|
||||
.suggestion-box li.active {
|
||||
background-color: #EEE; }
|
||||
|
||||
.comments-container {
|
||||
width: 100%;
|
||||
border-top: 1px solid #DDD;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 16px; }
|
||||
.comments-container h5 {
|
||||
color: #888;
|
||||
font-weight: normal;
|
||||
margin-top: 0.5em; }
|
||||
|
||||
.comment-editor .CodeMirror, .comment-editor .CodeMirror-scroll {
|
||||
min-height: 175px; }
|
||||
|
||||
|
||||
234
public/dist/styles.css
vendored
234
public/dist/styles.css
vendored
@@ -144,6 +144,11 @@ a, .link, .text-button {
|
||||
position: relative;
|
||||
display: inline-block; }
|
||||
|
||||
.blended-links a {
|
||||
color: inherit; }
|
||||
.blended-links a svg {
|
||||
fill: currentColor; }
|
||||
|
||||
/*
|
||||
* Other HTML Text Elements
|
||||
*/
|
||||
@@ -458,6 +463,8 @@ body.flexbox {
|
||||
background-color: #F2F2F2;
|
||||
max-width: 360px;
|
||||
min-height: 90vh; }
|
||||
.flex.sidebar section {
|
||||
margin: 16px; }
|
||||
|
||||
.flex.sidebar + .flex.content {
|
||||
flex: 3;
|
||||
@@ -1214,7 +1221,7 @@ div[class^="col-"] img {
|
||||
display: block;
|
||||
position: relative; }
|
||||
.callout:before {
|
||||
background-image: url("/icon/info-filled.svg?color=015380");
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMwMTUzODAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTEgMTVoLTJ2LTZoMnY2em0wLThoLTJWN2gydjJ6Ii8+PC9zdmc+");
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
width: 1.2em;
|
||||
@@ -1231,13 +1238,13 @@ div[class^="col-"] img {
|
||||
background-color: #e7f3e7;
|
||||
color: #376c39; }
|
||||
.callout.success:before {
|
||||
background-image: url("/icon/check-circle.svg?color=376c39"); }
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMzNzZjMzkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bS0yIDE1bC01LTUgMS40MS0xLjQxTDEwIDE0LjE3bDcuNTktNy41OUwxOSA4bC05IDl6Ii8+PC9zdmc+"); }
|
||||
.callout.danger {
|
||||
border-left-color: #E84F4F;
|
||||
background-color: #fce8e8;
|
||||
color: #b91818; }
|
||||
.callout.danger:before {
|
||||
background-image: url("/icon/danger.svg?color=b91818"); }
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiOTE4MTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0xNS43MyAzSDguMjdMMyA4LjI3djcuNDZMOC4yNyAyMWg3LjQ2TDIxIDE1LjczVjguMjdMMTUuNzMgM3pNMTIgMTcuM2MtLjcyIDAtMS4zLS41OC0xLjMtMS4zIDAtLjcyLjU4LTEuMyAxLjMtMS4zLjcyIDAgMS4zLjU4IDEuMyAxLjMgMCAuNzItLjU4IDEuMy0xLjMgMS4zem0xLTQuM2gtMlY3aDJ2NnoiLz4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg=="); }
|
||||
.callout.info {
|
||||
border-left-color: #0288D1;
|
||||
background-color: #d3efff;
|
||||
@@ -1247,7 +1254,7 @@ div[class^="col-"] img {
|
||||
background-color: #faeae0;
|
||||
color: #b6531c; }
|
||||
.callout.warning:before {
|
||||
background-image: url("/icon/warning.svg?color=b6531c"); }
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiNjUzMWMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEgMjFoMjJMMTIgMiAxIDIxem0xMi0zaC0ydi0yaDJ2MnptMC00aC0ydi00aDJ2NHoiLz48L3N2Zz4="); }
|
||||
|
||||
.card {
|
||||
margin: 16px;
|
||||
@@ -1270,6 +1277,9 @@ div[class^="col-"] img {
|
||||
word-wrap: break-word;
|
||||
word-break: break-word; }
|
||||
|
||||
.sidebar .card h3, .sidebar .card .body, .sidebar .card .empty-text {
|
||||
padding: 12px 16px; }
|
||||
|
||||
.card.drag-card {
|
||||
border: 1px solid #DDD;
|
||||
border-radius: 4px;
|
||||
@@ -1315,6 +1325,30 @@ div[class^="col-"] img {
|
||||
padding: 16px;
|
||||
border: 1px solid #DDD; }
|
||||
|
||||
.tag-item {
|
||||
display: inline-flex;
|
||||
margin-bottom: 6px;
|
||||
margin-right: 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #CCC;
|
||||
overflow: hidden;
|
||||
font-size: 0.85em; }
|
||||
.tag-item a, .tag-item a:hover, .tag-item a:active {
|
||||
padding: 4px 8px;
|
||||
color: #777;
|
||||
transition: background-color ease-in-out 80ms;
|
||||
text-decoration: none; }
|
||||
.tag-item a:hover {
|
||||
background-color: rgba(255, 255, 255, 0.7); }
|
||||
.tag-item svg {
|
||||
fill: #888; }
|
||||
.tag-item .tag-value {
|
||||
border-left: 1px solid #DDD;
|
||||
background-color: rgba(255, 255, 255, 0.5); }
|
||||
|
||||
.tag-list div:last-child .tag-item {
|
||||
margin-bottom: 0; }
|
||||
|
||||
.button-base, .button, input[type="button"], input[type="submit"] {
|
||||
text-decoration: none;
|
||||
font-size: 15px;
|
||||
@@ -1335,13 +1369,13 @@ div[class^="col-"] img {
|
||||
text-transform: uppercase;
|
||||
border: 1px solid #0288D1;
|
||||
vertical-align: top; }
|
||||
.button-base:hover, .button:hover, input[type="button"]:hover, input[type="submit"]:hover {
|
||||
.button-base:hover, .button:hover, input:hover[type="button"], input:hover[type="submit"] {
|
||||
background-color: #02a2f9;
|
||||
text-decoration: none;
|
||||
color: #EEE; }
|
||||
.button-base:active, .button:active, input[type="button"]:active, input[type="submit"]:active {
|
||||
.button-base:active, .button:active, input:active[type="button"], input:active[type="submit"] {
|
||||
background-color: #026ea9; }
|
||||
.button-base:focus, .button:focus, input[type="button"]:focus, input[type="submit"]:focus {
|
||||
.button-base:focus, .button:focus, input:focus[type="button"], input:focus[type="submit"] {
|
||||
background-color: #0295e5;
|
||||
box-shadow: 0 0 4px 1px #CCC;
|
||||
text-decoration: none;
|
||||
@@ -1596,9 +1630,9 @@ table.list-table {
|
||||
border: 1px solid #E84F4F; }
|
||||
.input-base.pos, .pos.fake-input, input.pos[type="text"], input.pos[type="number"], input.pos[type="email"], input.pos[type="date"], input.pos[type="search"], input.pos[type="url"], input.pos[type="password"], select.pos, textarea.pos, .input-base.valid, .valid.fake-input, input.valid[type="text"], input.valid[type="number"], input.valid[type="email"], input.valid[type="date"], input.valid[type="search"], input.valid[type="url"], input.valid[type="password"], select.valid, textarea.valid {
|
||||
border: 1px solid #52A256; }
|
||||
.input-base.disabled, .disabled.fake-input, input.disabled[type="text"], input.disabled[type="number"], input.disabled[type="email"], input.disabled[type="date"], input.disabled[type="search"], input.disabled[type="url"], input.disabled[type="password"], select.disabled, textarea.disabled, .input-base[disabled], [disabled].fake-input, input[disabled][type="text"], input[disabled][type="number"], input[disabled][type="email"], input[disabled][type="date"], input[disabled][type="search"], input[disabled][type="url"], input[disabled][type="password"], select[disabled], textarea[disabled] {
|
||||
.input-base.disabled, .disabled.fake-input, input.disabled[type="text"], input.disabled[type="number"], input.disabled[type="email"], input.disabled[type="date"], input.disabled[type="search"], input.disabled[type="url"], input.disabled[type="password"], select.disabled, textarea.disabled, .input-base[disabled], .fake-input[disabled], input[disabled][type="text"], input[disabled][type="number"], input[disabled][type="email"], input[disabled][type="date"], input[disabled][type="search"], input[disabled][type="url"], input[disabled][type="password"], select[disabled], textarea[disabled] {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAMUlEQVQIW2NkwAGuXbv2nxGbHEhCS0uLEUMSJgHShCKJLIEiiS4Bl8QmAZbEJQGSBAC62BuJ+tt7zgAAAABJRU5ErkJggg==); }
|
||||
.input-base:focus, .fake-input:focus, input[type="text"]:focus, input[type="number"]:focus, input[type="email"]:focus, input[type="date"]:focus, input[type="search"]:focus, input[type="url"]:focus, input[type="password"]:focus, select:focus, textarea:focus {
|
||||
.input-base:focus, .fake-input:focus, input:focus[type="text"], input:focus[type="number"], input:focus[type="email"], input:focus[type="date"], input:focus[type="search"], input:focus[type="url"], input:focus[type="password"], select:focus, textarea:focus {
|
||||
outline: 0; }
|
||||
|
||||
.fake-input {
|
||||
@@ -2566,49 +2600,87 @@ span.CodeMirror-selectedtext {
|
||||
width: 100%;
|
||||
height: 100%; }
|
||||
|
||||
/**
|
||||
* Custom Copy Button
|
||||
*/
|
||||
.CodeMirror-copy {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
background-color: #EEE;
|
||||
padding: 6px;
|
||||
line-height: 0;
|
||||
border: 1px solid #DDD;
|
||||
cursor: pointer;
|
||||
fill: #444;
|
||||
z-index: 5;
|
||||
transition: all ease-in 180ms;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
opacity: 0.7; }
|
||||
.CodeMirror-copy svg {
|
||||
transition: -webkit-transform ease-in 180ms;
|
||||
transition: transform ease-in 180ms;
|
||||
transition: transform ease-in 180ms, -webkit-transform ease-in 180ms;
|
||||
-webkit-transform: translateY(0);
|
||||
transform: translateY(0); }
|
||||
.CodeMirror-copy.success {
|
||||
background-color: #70b774;
|
||||
fill: #FFF; }
|
||||
.CodeMirror-copy.success svg {
|
||||
-webkit-transform: translateY(-3px);
|
||||
transform: translateY(-3px); }
|
||||
|
||||
.CodeMirror:hover .CodeMirror-copy {
|
||||
-webkit-user-select: all;
|
||||
-moz-user-select: all;
|
||||
-ms-user-select: all;
|
||||
user-select: all;
|
||||
opacity: 1; }
|
||||
|
||||
[notification] {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 64px 32px;
|
||||
padding: 24px 32px;
|
||||
padding: 16px 24px;
|
||||
background-color: #EEE;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 3px 1px rgba(76, 76, 76, 0.26);
|
||||
box-shadow: 0 1px 3px 1px rgba(76, 76, 76, 0.26), 0 1px 12px 0px rgba(76, 76, 76, 0.2);
|
||||
z-index: 999999;
|
||||
cursor: pointer;
|
||||
max-width: 360px;
|
||||
transition: -webkit-transform ease-in-out 280ms;
|
||||
transition: transform ease-in-out 280ms;
|
||||
transition: transform ease-in-out 280ms, -webkit-transform ease-in-out 280ms;
|
||||
-webkit-transform: translate3d(580px, 0, 0);
|
||||
transform: translate3d(580px, 0, 0);
|
||||
-webkit-transform: translateX(580px);
|
||||
transform: translateX(580px);
|
||||
display: grid;
|
||||
grid-template-columns: 64px 1fr; }
|
||||
grid-template-columns: 42px 1fr;
|
||||
color: #FFF; }
|
||||
[notification] span, [notification] svg {
|
||||
vertical-align: middle;
|
||||
justify-self: center;
|
||||
align-self: center; }
|
||||
[notification] svg {
|
||||
fill: #EEEEEE;
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
padding-right: 16px; }
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
padding-right: 12px; }
|
||||
[notification] span {
|
||||
vertical-align: middle;
|
||||
line-height: 1.3; }
|
||||
[notification].pos {
|
||||
background-color: #52A256;
|
||||
color: #EEE; }
|
||||
background-color: #52A256; }
|
||||
[notification].neg {
|
||||
background-color: #E84F4F;
|
||||
color: #EEE; }
|
||||
background-color: #E84F4F; }
|
||||
[notification].warning {
|
||||
background-color: #e27b41;
|
||||
color: #EEE; }
|
||||
background-color: #e27b41; }
|
||||
[notification].showing {
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0); }
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0); }
|
||||
[notification].showing:hover {
|
||||
-webkit-transform: translate3d(0, -2px, 0);
|
||||
transform: translate3d(0, -2px, 0); }
|
||||
@@ -2702,7 +2774,8 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
|
||||
.dropzone-container {
|
||||
position: relative;
|
||||
border: 3px dashed #DDD; }
|
||||
background-color: #EEE;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='%23a9a9a9' fill-opacity='0.52' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E"); }
|
||||
|
||||
.image-manager-list .image {
|
||||
display: block;
|
||||
@@ -2714,13 +2787,13 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
width: 16.66667%;
|
||||
height: auto;
|
||||
border: 1px solid #DDD;
|
||||
box-shadow: 0 0 0 0 transparent;
|
||||
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
|
||||
transition: all cubic-bezier(0.4, 0, 1, 1) 160ms;
|
||||
overflow: hidden; }
|
||||
.image-manager-list .image.selected {
|
||||
-webkit-transform: scale3d(0.92, 0.92, 0.92);
|
||||
transform: scale3d(0.92, 0.92, 0.92);
|
||||
border: 1px solid #444;
|
||||
border: 4px solid #FFF;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2); }
|
||||
.image-manager-list .image img {
|
||||
width: 100%;
|
||||
@@ -2758,12 +2831,26 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
.image-manager-sidebar {
|
||||
width: 300px;
|
||||
margin-left: 1px;
|
||||
padding: 16px 24px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
border-left: 1px solid #DDD; }
|
||||
.image-manager-sidebar .inner {
|
||||
padding: 16px; }
|
||||
.image-manager-sidebar img {
|
||||
max-width: 100%;
|
||||
max-height: 180px;
|
||||
display: block;
|
||||
margin: 0 auto 16px auto;
|
||||
box-shadow: 0 1px 21px 1px rgba(76, 76, 76, 0.3); }
|
||||
.image-manager-sidebar .image-manager-viewer {
|
||||
height: 196px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center; }
|
||||
.image-manager-sidebar .image-manager-viewer a {
|
||||
display: inline-block; }
|
||||
.image-manager-sidebar .dropzone-container {
|
||||
margin-top: 16px; }
|
||||
border-bottom: 1px solid #DDD; }
|
||||
|
||||
.image-manager-list {
|
||||
overflow-y: scroll;
|
||||
@@ -2783,10 +2870,10 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
* Copyright (c) 2012 Matias Meno <m@tias.me>
|
||||
*/
|
||||
.dz-message {
|
||||
font-size: 1.2em;
|
||||
line-height: 1.1;
|
||||
font-size: 1em;
|
||||
line-height: 2.35;
|
||||
font-style: italic;
|
||||
color: #aaa;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
padding: 24px 16px;
|
||||
@@ -3092,6 +3179,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
height: 200px; } }
|
||||
|
||||
.comment-box {
|
||||
clear: left;
|
||||
border: 1px solid #DDD;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 3px; }
|
||||
@@ -3141,20 +3229,21 @@ header {
|
||||
header .links {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-right: 32px; }
|
||||
@media screen and (max-width: 992px) {
|
||||
header .links {
|
||||
margin-right: 16px; } }
|
||||
margin-left: 16px; }
|
||||
header .links a {
|
||||
display: inline-block;
|
||||
padding: 16px 24px;
|
||||
padding: 16px;
|
||||
color: #FFF;
|
||||
fill: #FFF; }
|
||||
header .links a:last-child {
|
||||
padding-right: 0; }
|
||||
@media screen and (max-width: 992px) {
|
||||
header .links a {
|
||||
padding: 16px 12px; } }
|
||||
header .dropdown-container {
|
||||
padding-left: 16px;
|
||||
padding-right: 0; }
|
||||
@media screen and (max-width: 992px) {
|
||||
header .links a {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px; }
|
||||
header .dropdown-container {
|
||||
padding-left: 12px; } }
|
||||
header .avatar, header .user-name {
|
||||
display: inline-block; }
|
||||
header .avatar {
|
||||
@@ -3200,10 +3289,13 @@ header .search-box {
|
||||
header .search-box input {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: #EEE; }
|
||||
header .search-box button {
|
||||
color: #EEE;
|
||||
fill: #EEE; }
|
||||
z-index: 2; }
|
||||
header .search-box button {
|
||||
fill: #EEE;
|
||||
z-index: 1; }
|
||||
header .search-box button svg {
|
||||
margin-right: 0; }
|
||||
header .search-box ::-webkit-input-placeholder {
|
||||
/* Chrome/Opera/Safari */
|
||||
color: #DDD; }
|
||||
@@ -3538,8 +3630,6 @@ ul.pagination {
|
||||
padding: 3px 12px;
|
||||
border: 1px solid #CCC;
|
||||
margin-left: -1px;
|
||||
color: #888;
|
||||
fill: #888;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
@@ -3547,13 +3637,7 @@ ul.pagination {
|
||||
ul.pagination a.disabled, ul.pagination span.disabled {
|
||||
cursor: not-allowed; }
|
||||
ul.pagination li.active span {
|
||||
background-color: rgba(2, 136, 209, 0.8);
|
||||
color: #EEE;
|
||||
fill: #EEE;
|
||||
border-color: rgba(2, 136, 209, 0.8); }
|
||||
ul.pagination a {
|
||||
color: #0288D1;
|
||||
fill: #0288D1; }
|
||||
color: #FFF; }
|
||||
|
||||
.compact ul.pagination {
|
||||
margin: 0; }
|
||||
@@ -3717,10 +3801,13 @@ ul.pagination {
|
||||
padding: 0 !important; }
|
||||
|
||||
.page-content {
|
||||
width: 100%;
|
||||
max-width: 840px;
|
||||
margin: 0 auto;
|
||||
margin-top: 48px;
|
||||
overflow-wrap: break-word; }
|
||||
.page-content.flex {
|
||||
margin-top: 16px; }
|
||||
.page-content .align-left {
|
||||
text-align: left; }
|
||||
.page-content img.align-left, .page-content table.align-left {
|
||||
@@ -3755,6 +3842,8 @@ ul.pagination {
|
||||
background: #dbffdb; }
|
||||
.page-content del {
|
||||
background: #FFECEC; }
|
||||
.page-content.page-revision pre code {
|
||||
white-space: pre-wrap; }
|
||||
|
||||
.pointer-container {
|
||||
position: relative;
|
||||
@@ -3771,8 +3860,10 @@ ul.pagination {
|
||||
position: absolute;
|
||||
top: -60px;
|
||||
background-color: #FFF;
|
||||
width: 272px;
|
||||
width: 275px;
|
||||
z-index: 55; }
|
||||
.pointer.is-page-editable {
|
||||
width: 328px; }
|
||||
.pointer:before {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
@@ -3796,12 +3887,13 @@ ul.pagination {
|
||||
color: #666;
|
||||
width: 172px;
|
||||
z-index: 40; }
|
||||
.pointer input, .pointer button {
|
||||
.pointer input, .pointer button, .pointer a {
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
height: 28px;
|
||||
font-size: 12px;
|
||||
vertical-align: top; }
|
||||
vertical-align: top;
|
||||
padding: 5px 16px; }
|
||||
.pointer > i {
|
||||
color: #888;
|
||||
font-size: 18px;
|
||||
@@ -3812,10 +3904,17 @@ ul.pagination {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none; }
|
||||
.pointer .button {
|
||||
.pointer .input-group .button {
|
||||
line-height: 1;
|
||||
margin: 0 0 0 -4px;
|
||||
box-shadow: none; }
|
||||
.pointer a.button {
|
||||
margin: 0 0 0 0; }
|
||||
.pointer a.button:hover {
|
||||
fill: #fff; }
|
||||
.pointer .svg-icon {
|
||||
width: 1.2em;
|
||||
height: 1.2em; }
|
||||
|
||||
.floating-toolbox {
|
||||
background-color: #FFF;
|
||||
@@ -3952,6 +4051,16 @@ ul.pagination {
|
||||
.suggestion-box li.active {
|
||||
background-color: #EEE; }
|
||||
|
||||
.comments-container {
|
||||
width: 100%;
|
||||
border-top: 1px solid #DDD;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 16px; }
|
||||
.comments-container h5 {
|
||||
color: #888;
|
||||
font-weight: normal;
|
||||
margin-top: 0.5em; }
|
||||
|
||||
.comment-editor .CodeMirror, .comment-editor .CodeMirror-scroll {
|
||||
min-height: 175px; }
|
||||
|
||||
@@ -4077,7 +4186,8 @@ body.dragging, body.dragging * {
|
||||
border: 1px solid #DDD;
|
||||
margin-left: -1px; }
|
||||
.contained-search-box input {
|
||||
flex: 5; }
|
||||
flex: 5;
|
||||
padding: 6px 12px; }
|
||||
.contained-search-box input:focus, .contained-search-box input:active {
|
||||
outline: 0; }
|
||||
.contained-search-box button {
|
||||
|
||||
1
resources/assets/icons/auth/discord.svg
Normal file
1
resources/assets/icons/auth/discord.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240"><style>.st0{fill:#7289DA;}</style><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
4
resources/assets/icons/spanner.svg
Normal file
4
resources/assets/icons/spanner.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path clip-rule="evenodd" fill="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
5
resources/assets/icons/star.svg
Normal file
5
resources/assets/icons/star.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 256 B |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 24 24" fill="#b6531c" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 191 B |
@@ -6,6 +6,12 @@ class BackToTop {
|
||||
this.targetElem = document.getElementById('header');
|
||||
this.showing = false;
|
||||
this.breakPoint = 1200;
|
||||
|
||||
if (document.body.classList.contains('flexbox')) {
|
||||
this.elem.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
this.elem.addEventListener('click', this.scrollToTop.bind(this));
|
||||
window.addEventListener('scroll', this.onPageScroll.bind(this));
|
||||
}
|
||||
|
||||
@@ -18,6 +18,13 @@ class MarkdownEditor {
|
||||
|
||||
this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
|
||||
this.init();
|
||||
|
||||
// Scroll to text if needed.
|
||||
const queryParams = (new URL(window.location)).searchParams;
|
||||
const scrollText = queryParams.get('content-text');
|
||||
if (scrollText) {
|
||||
this.scrollToText(scrollText);
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -52,6 +59,10 @@ class MarkdownEditor {
|
||||
let action = button.getAttribute('data-action');
|
||||
if (action === 'insertImage') this.actionInsertImage();
|
||||
if (action === 'insertLink') this.actionShowLinkSelector();
|
||||
if (action === 'insertDrawing' && event.ctrlKey) {
|
||||
this.actionShowImageManager();
|
||||
return;
|
||||
}
|
||||
if (action === 'insertDrawing') this.actionStartDrawing();
|
||||
});
|
||||
|
||||
@@ -293,7 +304,14 @@ class MarkdownEditor {
|
||||
this.cm.focus();
|
||||
this.cm.replaceSelection(newText);
|
||||
this.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length);
|
||||
});
|
||||
}, 'gallery');
|
||||
}
|
||||
|
||||
actionShowImageManager() {
|
||||
let cursorPos = this.cm.getCursor('from');
|
||||
window.ImageManager.show(image => {
|
||||
this.insertDrawing(image, cursorPos);
|
||||
}, 'drawio');
|
||||
}
|
||||
|
||||
// Show the popup link selector and insert a link when finished
|
||||
@@ -324,10 +342,7 @@ class MarkdownEditor {
|
||||
};
|
||||
|
||||
window.$http.post(window.baseUrl('/images/drawing/upload'), data).then(resp => {
|
||||
let newText = `<div drawio-diagram="${resp.data.id}"><img src="${resp.data.url}"></div>`;
|
||||
this.cm.focus();
|
||||
this.cm.replaceSelection(newText);
|
||||
this.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length);
|
||||
this.insertDrawing(resp.data, cursorPos);
|
||||
DrawIO.close();
|
||||
}).catch(err => {
|
||||
window.$events.emit('error', trans('errors.image_upload_error'));
|
||||
@@ -336,6 +351,13 @@ class MarkdownEditor {
|
||||
});
|
||||
}
|
||||
|
||||
insertDrawing(image, originalCursor) {
|
||||
let newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`;
|
||||
this.cm.focus();
|
||||
this.cm.replaceSelection(newText);
|
||||
this.cm.setCursor(originalCursor.line, originalCursor.ch + newText.length);
|
||||
}
|
||||
|
||||
// Show draw.io if enabled and handle save.
|
||||
actionEditDrawing(imgContainer) {
|
||||
if (document.querySelector('[drawio-enabled]').getAttribute('drawio-enabled') !== 'true') return;
|
||||
@@ -353,8 +375,8 @@ class MarkdownEditor {
|
||||
uploaded_to: Number(document.getElementById('page-editor').getAttribute('page-id'))
|
||||
};
|
||||
|
||||
window.$http.put(window.baseUrl(`/images/drawing/upload/${drawingId}`), data).then(resp => {
|
||||
let newText = `<div drawio-diagram="${resp.data.id}"><img src="${resp.data.url + `?updated=${Date.now()}`}"></div>`;
|
||||
window.$http.post(window.baseUrl(`/images/drawing/upload`), data).then(resp => {
|
||||
let newText = `<div drawio-diagram="${resp.data.id}"><img src="${resp.data.url}"></div>`;
|
||||
let newContent = this.cm.getValue().split('\n').map(line => {
|
||||
if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) {
|
||||
return newText;
|
||||
@@ -372,6 +394,33 @@ class MarkdownEditor {
|
||||
});
|
||||
}
|
||||
|
||||
// Scroll to a specified text
|
||||
scrollToText(searchText) {
|
||||
if (!searchText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = this.cm.getValue();
|
||||
const lines = content.split(/\r?\n/);
|
||||
let lineNumber = lines.findIndex(line => {
|
||||
return line && line.indexOf(searchText) !== -1;
|
||||
});
|
||||
|
||||
if (lineNumber === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cm.scrollIntoView({
|
||||
line: lineNumber,
|
||||
}, 200);
|
||||
this.cm.focus();
|
||||
// set the cursor location.
|
||||
this.cm.setCursor({
|
||||
line: lineNumber,
|
||||
char: lines[lineNumber].length
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = MarkdownEditor ;
|
||||
@@ -6,11 +6,16 @@ class Notification {
|
||||
this.type = elem.getAttribute('notification');
|
||||
this.textElem = elem.querySelector('span');
|
||||
this.autohide = this.elem.hasAttribute('data-autohide');
|
||||
this.elem.style.display = 'grid';
|
||||
|
||||
window.$events.listen(this.type, text => {
|
||||
this.show(text);
|
||||
});
|
||||
elem.addEventListener('click', this.hide.bind(this));
|
||||
if (elem.hasAttribute('data-show')) this.show(this.textElem.textContent);
|
||||
|
||||
if (elem.hasAttribute('data-show')) {
|
||||
setTimeout(() => this.show(this.textElem.textContent), 100);
|
||||
}
|
||||
|
||||
this.hideCleanup = this.hideCleanup.bind(this);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class PageDisplay {
|
||||
|
||||
// Sidebar page nav click event
|
||||
$('.sidebar-page-nav').on('click', 'a', event => {
|
||||
goToText(event.target.getAttribute('href').substr(1));
|
||||
this.goToText(event.target.getAttribute('href').substr(1));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class PageDisplay {
|
||||
}
|
||||
|
||||
setupPointer() {
|
||||
if (document.getElementById('pointer') === null) return;
|
||||
// Set up pointer
|
||||
let $pointer = $('#pointer').detach();
|
||||
let pointerShowing = false;
|
||||
@@ -73,11 +74,23 @@ class PageDisplay {
|
||||
pointerShowing = false;
|
||||
});
|
||||
|
||||
let updatePointerContent = () => {
|
||||
let updatePointerContent = ($elem) => {
|
||||
let inputText = pointerModeLink ? window.baseUrl(`/link/${this.pageId}#${pointerSectionId}`) : `{{@${this.pageId}#${pointerSectionId}}}`;
|
||||
if (pointerModeLink && inputText.indexOf('http') !== 0) inputText = window.location.protocol + "//" + window.location.host + inputText;
|
||||
|
||||
$pointer.find('input').val(inputText);
|
||||
|
||||
// update anchor if present
|
||||
const $editAnchor = $pointer.find('#pointer-edit');
|
||||
if ($editAnchor.length !== 0 && $elem) {
|
||||
const editHref = $editAnchor.data('editHref');
|
||||
const element = $elem[0];
|
||||
const elementId = element.id;
|
||||
|
||||
// get the first 50 characters.
|
||||
let queryContent = element.textContent && element.textContent.substring(0, 50);
|
||||
$editAnchor[0].href = `${editHref}?content-id=${elementId}&content-text=${encodeURIComponent(queryContent)}`;
|
||||
}
|
||||
};
|
||||
|
||||
// Show pointer when selecting a single block of tagged content
|
||||
@@ -89,7 +102,7 @@ class PageDisplay {
|
||||
// Show pointer and set link
|
||||
let $elem = $(this);
|
||||
pointerSectionId = $elem.attr('id');
|
||||
updatePointerContent();
|
||||
updatePointerContent($elem);
|
||||
|
||||
$elem.before($pointer);
|
||||
$pointer.show();
|
||||
@@ -218,7 +231,6 @@ class PageDisplay {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = PageDisplay;
|
||||
module.exports = PageDisplay;
|
||||
|
||||
@@ -221,8 +221,6 @@ function codePlugin() {
|
||||
|
||||
function drawIoPlugin() {
|
||||
|
||||
const drawIoUrl = 'https://www.draw.io/?embed=1&ui=atlas&spin=1&proto=json';
|
||||
let iframe = null;
|
||||
let pageEditor = null;
|
||||
let currentNode = null;
|
||||
|
||||
@@ -230,6 +228,22 @@ function drawIoPlugin() {
|
||||
return node.hasAttribute('drawio-diagram');
|
||||
}
|
||||
|
||||
function showDrawingManager(mceEditor, selectedNode = null) {
|
||||
pageEditor = mceEditor;
|
||||
currentNode = selectedNode;
|
||||
// Show image manager
|
||||
window.ImageManager.show(function (image) {
|
||||
if (selectedNode) {
|
||||
let imgElem = selectedNode.querySelector('img');
|
||||
pageEditor.dom.setAttrib(imgElem, 'src', image.url);
|
||||
pageEditor.dom.setAttrib(selectedNode, 'drawio-diagram', image.id);
|
||||
} else {
|
||||
let imgHTML = `<div drawio-diagram="${image.id}" contenteditable="false"><img src="${image.url}"></div>`;
|
||||
pageEditor.insertContent(imgHTML);
|
||||
}
|
||||
}, 'drawio');
|
||||
}
|
||||
|
||||
function showDrawingEditor(mceEditor, selectedNode = null) {
|
||||
pageEditor = mceEditor;
|
||||
currentNode = selectedNode;
|
||||
@@ -248,9 +262,9 @@ function drawIoPlugin() {
|
||||
if (currentNode) {
|
||||
DrawIO.close();
|
||||
let imgElem = currentNode.querySelector('img');
|
||||
let drawingId = currentNode.getAttribute('drawio-diagram');
|
||||
window.$http.put(window.baseUrl(`/images/drawing/upload/${drawingId}`), data).then(resp => {
|
||||
pageEditor.dom.setAttrib(imgElem, 'src', `${resp.data.url}?updated=${Date.now()}`);
|
||||
window.$http.post(window.baseUrl(`/images/drawing/upload`), data).then(resp => {
|
||||
pageEditor.dom.setAttrib(imgElem, 'src', resp.data.url);
|
||||
pageEditor.dom.setAttrib(currentNode, 'drawio-diagram', resp.data.id);
|
||||
}).catch(err => {
|
||||
window.$events.emit('error', trans('errors.image_upload_error'));
|
||||
console.log(err);
|
||||
@@ -287,13 +301,24 @@ function drawIoPlugin() {
|
||||
window.tinymce.PluginManager.add('drawio', function(editor, url) {
|
||||
|
||||
editor.addCommand('drawio', () => {
|
||||
showDrawingEditor(editor);
|
||||
let selectedNode = editor.selection.getNode();
|
||||
showDrawingEditor(editor, isDrawing(selectedNode) ? selectedNode : null);
|
||||
});
|
||||
|
||||
editor.addButton('drawio', {
|
||||
type: 'splitbutton',
|
||||
tooltip: 'Drawing',
|
||||
image: window.baseUrl('/icon/drawing.svg?color=000000'),
|
||||
cmd: 'drawio'
|
||||
image: `data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMwMDAwMDAiICB4bWxucz0iaHR0cDovL3d3 dy53My5vcmcvMjAwMC9zdmciPgogICAgPHBhdGggZD0iTTIzIDdWMWgtNnYySDdWMUgxdjZoMnYx MEgxdjZoNnYtMmgxMHYyaDZ2LTZoLTJWN2gyek0zIDNoMnYySDNWM3ptMiAxOEgzdi0yaDJ2Mnpt MTItMkg3di0ySDVWN2gyVjVoMTB2MmgydjEwaC0ydjJ6bTQgMmgtMnYtMmgydjJ6TTE5IDVWM2gy djJoLTJ6bS01LjI3IDloLTMuNDlsLS43MyAySDcuODlsMy40LTloMS40bDMuNDEgOWgtMS42M2wt Ljc0LTJ6bS0zLjA0LTEuMjZoMi42MUwxMiA4LjkxbC0xLjMxIDMuODN6Ii8+CiAgICA8cGF0aCBk PSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+Cjwvc3ZnPg==`,
|
||||
cmd: 'drawio',
|
||||
menu: [
|
||||
{
|
||||
text: 'Drawing Manager',
|
||||
onclick() {
|
||||
let selectedNode = editor.selection.getNode();
|
||||
showDrawingManager(editor, isDrawing(selectedNode) ? selectedNode : null);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
editor.on('dblclick', event => {
|
||||
@@ -443,7 +468,7 @@ class WysiwygEditor {
|
||||
html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
|
||||
html += '</a>';
|
||||
win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
|
||||
});
|
||||
}, 'gallery');
|
||||
}
|
||||
|
||||
},
|
||||
@@ -458,13 +483,36 @@ class WysiwygEditor {
|
||||
},
|
||||
setup: function (editor) {
|
||||
|
||||
editor.on('init ExecCommand change input NodeChange ObjectResized', editorChange);
|
||||
editor.on('ExecCommand change input NodeChange ObjectResized', editorChange);
|
||||
|
||||
editor.on('init', () => {
|
||||
editorChange();
|
||||
// Scroll to the content if needed.
|
||||
const queryParams = (new URL(window.location)).searchParams;
|
||||
const scrollId = queryParams.get('content-id');
|
||||
if (scrollId) {
|
||||
scrollToText(scrollId);
|
||||
}
|
||||
});
|
||||
|
||||
function editorChange() {
|
||||
let content = editor.getContent();
|
||||
window.$events.emit('editor-html-change', content);
|
||||
}
|
||||
|
||||
function scrollToText(scrollId) {
|
||||
const element = editor.dom.get(encodeURIComponent(scrollId).replace(/!/g, '%21'));
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// scroll the element into the view and put the cursor at the end.
|
||||
element.scrollIntoView();
|
||||
editor.selection.select(element, true);
|
||||
editor.selection.collapse(false);
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
window.$events.listen('editor-html-update', html => {
|
||||
editor.setContent(html);
|
||||
editor.selection.select(editor.getBody(), true);
|
||||
@@ -522,7 +570,7 @@ class WysiwygEditor {
|
||||
html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
|
||||
html += '</a>';
|
||||
editor.execCommand('mceInsertContent', false, html);
|
||||
});
|
||||
}, 'gallery');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Global Polyfills
|
||||
import "babel-polyfill"
|
||||
import "./services/dom-polyfills"
|
||||
|
||||
// Url retrieval function
|
||||
|
||||
@@ -16,17 +16,19 @@ require('codemirror/mode/toml/toml');
|
||||
require('codemirror/mode/xml/xml');
|
||||
require('codemirror/mode/yaml/yaml');
|
||||
|
||||
const Clipboard = require("clipboard");
|
||||
|
||||
const CodeMirror = require('codemirror');
|
||||
|
||||
const modeMap = {
|
||||
css: 'css',
|
||||
c: 'clike',
|
||||
java: 'clike',
|
||||
scala: 'clike',
|
||||
kotlin: 'clike',
|
||||
'c++': 'clike',
|
||||
'c#': 'clike',
|
||||
csharp: 'clike',
|
||||
c: 'text/x-csrc',
|
||||
java: 'text/x-java',
|
||||
scala: 'text/x-scala',
|
||||
kotlin: 'text/x-kotlin',
|
||||
'c++': 'text/x-c++src',
|
||||
'c#': 'text/x-csharp',
|
||||
csharp: 'text/x-csharp',
|
||||
diff: 'diff',
|
||||
go: 'go',
|
||||
html: 'htmlmixed',
|
||||
@@ -77,7 +79,7 @@ function highlightElem(elem) {
|
||||
elem.innerHTML = elem.innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
|
||||
let content = elem.textContent.trim();
|
||||
|
||||
CodeMirror(function(elt) {
|
||||
let cm = CodeMirror(function(elt) {
|
||||
elem.parentNode.replaceChild(elt, elem);
|
||||
}, {
|
||||
value: content,
|
||||
@@ -86,6 +88,33 @@ function highlightElem(elem) {
|
||||
theme: getTheme(),
|
||||
readOnly: true
|
||||
});
|
||||
|
||||
addCopyIcon(cm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a button to a CodeMirror instance which copies the contents to the clipboard upon click.
|
||||
* @param cmInstance
|
||||
*/
|
||||
function addCopyIcon(cmInstance) {
|
||||
const copyIcon = `<svg viewBox="0 0 24 24" width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`;
|
||||
const copyButton = document.createElement('div');
|
||||
copyButton.classList.add('CodeMirror-copy');
|
||||
copyButton.innerHTML = copyIcon;
|
||||
cmInstance.display.wrapper.appendChild(copyButton);
|
||||
|
||||
const clipboard = new Clipboard(copyButton, {
|
||||
text: function(trigger) {
|
||||
return cmInstance.getValue()
|
||||
}
|
||||
});
|
||||
|
||||
clipboard.on('success', event => {
|
||||
copyButton.classList.add('success');
|
||||
setTimeout(() => {
|
||||
copyButton.classList.remove('success');
|
||||
}, 360);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,4 +12,13 @@ export function utcTimeStampToLocalTime(timestamp) {
|
||||
let hours = date.getHours();
|
||||
let mins = date.getMinutes();
|
||||
return `${(hours>9?'':'0') + hours}:${(mins>9?'':'0') + mins}`;
|
||||
}
|
||||
|
||||
export function formatDateTime(date) {
|
||||
let month = date.getMonth() + 1;
|
||||
let day = date.getDate();
|
||||
let hours = date.getHours();
|
||||
let mins = date.getMinutes();
|
||||
|
||||
return `${date.getFullYear()}-${(month>9?'':'0') + month}-${(day>9?'':'0') + day} ${(hours>9?'':'0') + hours}:${(mins>9?'':'0') + mins}`;
|
||||
}
|
||||
@@ -31,6 +31,9 @@ let methods = {
|
||||
},
|
||||
|
||||
getFileUrl(file) {
|
||||
if (file.external && file.path.indexOf('http') !== 0) {
|
||||
return file.path;
|
||||
}
|
||||
return window.baseUrl(`/attachments/${file.id}`);
|
||||
},
|
||||
|
||||
@@ -49,7 +52,9 @@ let methods = {
|
||||
},
|
||||
|
||||
deleteFile(file) {
|
||||
if (!file.deleting) return file.deleting = true;
|
||||
if (!file.deleting) {
|
||||
return this.$set(file, 'deleting', true);
|
||||
}
|
||||
|
||||
this.$http.delete(window.baseUrl(`/attachments/${file.id}`)).then(resp => {
|
||||
this.$events.emit('success', resp.data.message);
|
||||
@@ -79,10 +84,8 @@ let methods = {
|
||||
},
|
||||
|
||||
checkValidationErrors(groupName, err) {
|
||||
console.error(err);
|
||||
if (typeof err.response.data === "undefined" && typeof err.response.data.validation === "undefined") return;
|
||||
this.errors[groupName] = err.response.data.validation;
|
||||
console.log(this.errors[groupName]);
|
||||
if (typeof err.response.data === "undefined" && typeof err.response.data === "undefined") return;
|
||||
this.errors[groupName] = err.response.data;
|
||||
},
|
||||
|
||||
getUploadUrl(file) {
|
||||
@@ -97,6 +100,7 @@ let methods = {
|
||||
|
||||
attachNewLink(file) {
|
||||
file.uploaded_to = this.pageId;
|
||||
this.errors.link = {};
|
||||
this.$http.post(window.baseUrl('/attachments/link'), file).then(resp => {
|
||||
this.files.push(resp.data);
|
||||
this.file = this.newFile();
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
|
||||
import * as Dates from "../services/dates";
|
||||
|
||||
const dropzone = require('./components/dropzone');
|
||||
|
||||
let page = 0;
|
||||
@@ -26,25 +29,32 @@ const data = {
|
||||
|
||||
imageUpdateSuccess: false,
|
||||
imageDeleteSuccess: false,
|
||||
deleteConfirm: false,
|
||||
};
|
||||
|
||||
const methods = {
|
||||
|
||||
show(providedCallback) {
|
||||
show(providedCallback, imageType = null) {
|
||||
callback = providedCallback;
|
||||
this.showing = true;
|
||||
this.$el.children[0].components.overlay.show();
|
||||
|
||||
// Get initial images if they have not yet been loaded in.
|
||||
if (dataLoaded) return;
|
||||
if (dataLoaded && imageType === this.imageType) return;
|
||||
if (imageType) {
|
||||
this.imageType = imageType;
|
||||
this.resetState();
|
||||
}
|
||||
this.fetchData();
|
||||
dataLoaded = true;
|
||||
},
|
||||
|
||||
hide() {
|
||||
if (this.$refs.dropzone) {
|
||||
this.$refs.dropzone.onClose();
|
||||
}
|
||||
this.showing = false;
|
||||
this.selectedImage = false;
|
||||
this.$refs.dropzone.onClose();
|
||||
this.$el.children[0].components.overlay.hide();
|
||||
},
|
||||
|
||||
@@ -62,13 +72,18 @@ const methods = {
|
||||
},
|
||||
|
||||
setView(viewName) {
|
||||
this.view = viewName;
|
||||
this.resetState();
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
resetState() {
|
||||
this.cancelSearch();
|
||||
this.images = [];
|
||||
this.hasMore = false;
|
||||
this.deleteConfirm = false;
|
||||
page = 0;
|
||||
this.view = viewName;
|
||||
baseUrl = window.baseUrl(`/images/${this.imageType}/${viewName}/`);
|
||||
this.fetchData();
|
||||
baseUrl = window.baseUrl(`/images/${this.imageType}/${this.view}/`);
|
||||
},
|
||||
|
||||
searchImages() {
|
||||
@@ -89,6 +104,7 @@ const methods = {
|
||||
},
|
||||
|
||||
cancelSearch() {
|
||||
if (!this.searching) return;
|
||||
this.searching = false;
|
||||
this.searchTerm = '';
|
||||
this.images = preSearchImages;
|
||||
@@ -105,6 +121,7 @@ const methods = {
|
||||
this.callbackAndHide(image);
|
||||
} else {
|
||||
this.selectedImage = image;
|
||||
this.deleteConfirm = false;
|
||||
this.dependantPages = false;
|
||||
}
|
||||
|
||||
@@ -134,22 +151,27 @@ const methods = {
|
||||
},
|
||||
|
||||
deleteImage() {
|
||||
let force = this.dependantPages !== false;
|
||||
let url = window.baseUrl('/images/' + this.selectedImage.id);
|
||||
if (force) url += '?force=true';
|
||||
this.$http.delete(url).then(response => {
|
||||
|
||||
if (!this.deleteConfirm) {
|
||||
let url = window.baseUrl(`/images/usage/${this.selectedImage.id}`);
|
||||
this.$http.get(url).then(resp => {
|
||||
this.dependantPages = resp.data;
|
||||
}).catch(console.error).then(() => {
|
||||
this.deleteConfirm = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.$http.delete(`/images/${this.selectedImage.id}`).then(resp => {
|
||||
this.images.splice(this.images.indexOf(this.selectedImage), 1);
|
||||
this.selectedImage = false;
|
||||
this.$events.emit('success', trans('components.image_delete_success'));
|
||||
}).catch(error=> {
|
||||
if (error.response.status === 400) {
|
||||
this.dependantPages = error.response.data;
|
||||
}
|
||||
this.deleteConfirm = false;
|
||||
});
|
||||
},
|
||||
|
||||
getDate(stringDate) {
|
||||
return new Date(stringDate);
|
||||
return Dates.formatDateTime(new Date(stringDate));
|
||||
},
|
||||
|
||||
uploadSuccess(event) {
|
||||
|
||||
@@ -99,7 +99,7 @@ let methods = {
|
||||
lastSave = Date.now();
|
||||
}, errorRes => {
|
||||
if (draftErroring) return;
|
||||
window.$events('error', trans('errors.page_draft_autosave_fail'));
|
||||
window.$events.emit('error', trans('errors.page_draft_autosave_fail'));
|
||||
draftErroring = true;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
display: block;
|
||||
position: relative;
|
||||
&:before {
|
||||
background-image: url("/icon/info-filled.svg?color=015380");
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMwMTUzODAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTEgMTVoLTJ2LTZoMnY2em0wLThoLTJWN2gydjJ6Ii8+PC9zdmc+');
|
||||
background-repeat: no-repeat;
|
||||
content: '';
|
||||
width: 1.2em;
|
||||
@@ -157,7 +157,7 @@
|
||||
color: darken($positive, 16%);
|
||||
}
|
||||
&.success:before {
|
||||
background-image: url("/icon/check-circle.svg?color=376c39");
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMzNzZjMzkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bS0yIDE1bC01LTUgMS40MS0xLjQxTDEwIDE0LjE3bDcuNTktNy41OUwxOSA4bC05IDl6Ii8+PC9zdmc+");
|
||||
}
|
||||
&.danger {
|
||||
border-left-color: $negative;
|
||||
@@ -165,7 +165,7 @@
|
||||
color: darken($negative, 20%);
|
||||
}
|
||||
&.danger:before {
|
||||
background-image: url("/icon/danger.svg?color=b91818");
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiOTE4MTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0xNS43MyAzSDguMjdMMyA4LjI3djcuNDZMOC4yNyAyMWg3LjQ2TDIxIDE1LjczVjguMjdMMTUuNzMgM3pNMTIgMTcuM2MtLjcyIDAtMS4zLS41OC0xLjMtMS4zIDAtLjcyLjU4LTEuMyAxLjMtMS4zLjcyIDAgMS4zLjU4IDEuMyAxLjMgMCAuNzItLjU4IDEuMy0xLjMgMS4zem0xLTQuM2gtMlY3aDJ2NnoiLz4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==");
|
||||
}
|
||||
&.info {
|
||||
border-left-color: $info;
|
||||
@@ -178,7 +178,7 @@
|
||||
color: darken($warning, 16%);
|
||||
}
|
||||
&.warning:before {
|
||||
background-image: url("/icon/warning.svg?color=b6531c");
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiNjUzMWMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEgMjFoMjJMMTIgMiAxIDIxem0xMi0zaC0ydi0yaDJ2MnptMC00aC0ydi00aDJ2NHoiLz48L3N2Zz4=");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +208,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar .card {
|
||||
h3, .body, .empty-text {
|
||||
padding: $-s $-m;
|
||||
}
|
||||
}
|
||||
|
||||
.card.drag-card {
|
||||
border: 1px solid #DDD;
|
||||
border-radius: 4px;
|
||||
@@ -262,3 +268,33 @@
|
||||
padding: $-m;
|
||||
border: 1px solid #DDD;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
display: inline-flex;
|
||||
margin-bottom: $-xs;
|
||||
margin-right: $-xs;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #CCC;
|
||||
overflow: hidden;
|
||||
font-size: 0.85em;
|
||||
a, a:hover, a:active {
|
||||
padding: 4px 8px;
|
||||
color: #777;
|
||||
transition: background-color ease-in-out 80ms;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
svg {
|
||||
fill: #888;
|
||||
}
|
||||
.tag-value {
|
||||
border-left: 1px solid #DDD;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.tag-list div:last-child .tag-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -403,4 +403,38 @@ span.CodeMirror-selectedtext { background: none; }
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Copy Button
|
||||
*/
|
||||
.CodeMirror-copy {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
background-color: #EEE;
|
||||
padding: $-xs;
|
||||
line-height: 0;
|
||||
border: 1px solid #DDD;
|
||||
cursor: pointer;
|
||||
fill: #444;
|
||||
z-index: 5;
|
||||
transition: all ease-in 180ms;
|
||||
user-select: none;
|
||||
opacity: 0.7;
|
||||
svg {
|
||||
transition: transform ease-in 180ms;
|
||||
transform: translateY(0);
|
||||
}
|
||||
&.success {
|
||||
background-color: lighten($positive, 10%);
|
||||
fill: #FFF;
|
||||
svg {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
}
|
||||
}
|
||||
.CodeMirror:hover .CodeMirror-copy {
|
||||
user-select: all;
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -4,17 +4,18 @@
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: $-xl*2 $-xl;
|
||||
padding: $-l $-xl;
|
||||
padding: $-m $-l;
|
||||
background-color: #EEE;
|
||||
border-radius: 3px;
|
||||
box-shadow: $bs-med;
|
||||
box-shadow: $bs-card;
|
||||
z-index: 999999;
|
||||
cursor: pointer;
|
||||
max-width: 360px;
|
||||
transition: transform ease-in-out 280ms;
|
||||
transform: translate3d(580px, 0, 0);
|
||||
transform: translateX(580px);
|
||||
display: grid;
|
||||
grid-template-columns: 64px 1fr;
|
||||
grid-template-columns: 42px 1fr;
|
||||
color: #FFF;
|
||||
span, svg {
|
||||
vertical-align: middle;
|
||||
justify-self: center;
|
||||
@@ -22,9 +23,9 @@
|
||||
}
|
||||
svg {
|
||||
fill: #EEEEEE;
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
padding-right: $-m;
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
padding-right: $-s;
|
||||
}
|
||||
span {
|
||||
vertical-align: middle;
|
||||
@@ -32,18 +33,15 @@
|
||||
}
|
||||
&.pos {
|
||||
background-color: $positive;
|
||||
color: #EEE;
|
||||
}
|
||||
&.neg {
|
||||
background-color: $negative;
|
||||
color: #EEE;
|
||||
}
|
||||
&.warning {
|
||||
background-color: $secondary;
|
||||
color: #EEE;
|
||||
}
|
||||
&.showing {
|
||||
transform: translate3d(0, 0, 0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
&.showing:hover {
|
||||
transform: translate3d(0, -2px, 0);
|
||||
@@ -146,7 +144,8 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
|
||||
.dropzone-container {
|
||||
position: relative;
|
||||
border: 3px dashed #DDD;
|
||||
background-color: #EEE;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='%23a9a9a9' fill-opacity='0.52' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.image-manager-list .image {
|
||||
@@ -163,8 +162,10 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
transition: all cubic-bezier(.4, 0, 1, 1) 160ms;
|
||||
overflow: hidden;
|
||||
&.selected {
|
||||
transform: scale3d(0.92, 0.92, 0.92);
|
||||
border: 1px solid #444;
|
||||
//transform: scale3d(0.92, 0.92, 0.92);
|
||||
border: 4px solid #FFF;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
img {
|
||||
@@ -210,12 +211,30 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
.image-manager-sidebar {
|
||||
width: 300px;
|
||||
margin-left: 1px;
|
||||
padding: $-m $-l;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
border-left: 1px solid #DDD;
|
||||
.inner {
|
||||
padding: $-m;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 180px;
|
||||
display: block;
|
||||
margin: 0 auto $-m auto;
|
||||
box-shadow: 0 1px 21px 1px rgba(76, 76, 76, 0.3);
|
||||
}
|
||||
.image-manager-viewer {
|
||||
height: 196px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
a {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.dropzone-container {
|
||||
margin-top: $-m;
|
||||
border-bottom: 1px solid #DDD;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,10 +261,10 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
* Copyright (c) 2012 Matias Meno <m@tias.me>
|
||||
*/
|
||||
.dz-message {
|
||||
font-size: 1.2em;
|
||||
line-height: 1.1;
|
||||
font-size: 1em;
|
||||
line-height: 2.35;
|
||||
font-style: italic;
|
||||
color: #aaa;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
padding: $-l $-m;
|
||||
@@ -565,6 +584,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
}
|
||||
|
||||
.comment-box {
|
||||
clear: left;
|
||||
border: 1px solid #DDD;
|
||||
margin-bottom: $-s;
|
||||
border-radius: 3px;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
color: #666;
|
||||
width: 250px;
|
||||
max-width: 100%;
|
||||
|
||||
&.neg, &.invalid {
|
||||
border: 1px solid $negative;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ body.flexbox {
|
||||
background-color: #F2F2F2;
|
||||
max-width: 360px;
|
||||
min-height: 90vh;
|
||||
section {
|
||||
margin: $-m;
|
||||
}
|
||||
}
|
||||
.flex.sidebar + .flex.content {
|
||||
flex: 3;
|
||||
|
||||
@@ -16,21 +16,25 @@ header {
|
||||
.links {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-right: $-xl;
|
||||
@include smaller-than($screen-md) {
|
||||
margin-right: $-m;
|
||||
}
|
||||
margin-left: $-m;
|
||||
}
|
||||
.links a {
|
||||
display: inline-block;
|
||||
padding: $-m $-l;
|
||||
padding: $-m;
|
||||
color: #FFF;
|
||||
fill: #FFF;
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
.dropdown-container {
|
||||
padding-left: $-m;
|
||||
padding-right: 0;
|
||||
}
|
||||
@include smaller-than($screen-md) {
|
||||
.links a {
|
||||
padding-left: $-s;
|
||||
padding-right: $-s;
|
||||
}
|
||||
@include smaller-than($screen-md) {
|
||||
padding: $-m $-s;
|
||||
.dropdown-container {
|
||||
padding-left: $-s;
|
||||
}
|
||||
}
|
||||
.avatar, .user-name {
|
||||
@@ -90,10 +94,14 @@ header .search-box {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: #EEE;
|
||||
z-index: 2;
|
||||
}
|
||||
button {
|
||||
color: #EEE;
|
||||
fill: #EEE;
|
||||
z-index: 1;
|
||||
svg {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
::-webkit-input-placeholder { /* Chrome/Opera/Safari */
|
||||
color: #DDD;
|
||||
|
||||
@@ -266,22 +266,13 @@ ul.pagination {
|
||||
padding: $-xxs $-s;
|
||||
border: 1px solid #CCC;
|
||||
margin-left: -1px;
|
||||
color: #888;
|
||||
fill: #888;
|
||||
user-select: none;
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
li.active span {
|
||||
background-color: rgba($primary, 0.8);
|
||||
color: #EEE;
|
||||
fill: #EEE;
|
||||
border-color: rgba($primary, 0.8);
|
||||
}
|
||||
a {
|
||||
color: $primary;
|
||||
fill: $primary;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,4 +436,4 @@ ul.pagination {
|
||||
font-size: .8em;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,10 +35,14 @@
|
||||
}
|
||||
|
||||
.page-content {
|
||||
width: 100%;
|
||||
max-width: 840px;
|
||||
margin: 0 auto;
|
||||
margin-top: $-xxl;
|
||||
overflow-wrap: break-word;
|
||||
&.flex {
|
||||
margin-top: $-m;
|
||||
}
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -85,6 +89,12 @@
|
||||
del {
|
||||
background: #FFECEC;
|
||||
}
|
||||
|
||||
&.page-revision {
|
||||
pre code {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Page content pointers
|
||||
@@ -103,8 +113,13 @@
|
||||
position: absolute;
|
||||
top: -60px;
|
||||
background-color:#FFF;
|
||||
width: 272px;
|
||||
width: 275px;
|
||||
z-index: 55;
|
||||
|
||||
&.is-page-editable {
|
||||
width: 328px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
@@ -128,12 +143,13 @@
|
||||
width: 172px;
|
||||
z-index: 40;
|
||||
}
|
||||
input, button {
|
||||
input, button, a {
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
height: 28px;
|
||||
font-size: 12px;
|
||||
vertical-align: top;
|
||||
padding: 5px 16px;
|
||||
}
|
||||
> i {
|
||||
color: #888;
|
||||
@@ -144,11 +160,22 @@
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.button {
|
||||
.input-group .button {
|
||||
line-height: 1;
|
||||
margin: 0 0 0 -4px;
|
||||
box-shadow: none;
|
||||
}
|
||||
a.button {
|
||||
margin: 0 0 0 0;
|
||||
|
||||
&:hover {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
.svg-icon {
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute form
|
||||
@@ -315,6 +342,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.comments-container {
|
||||
width: 100%;
|
||||
border-top: 1px solid #DDD;
|
||||
margin-top: $-xl;
|
||||
margin-bottom: $-m;
|
||||
h5 {
|
||||
color: #888;
|
||||
font-weight: normal;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-editor .CodeMirror, .comment-editor .CodeMirror-scroll {
|
||||
min-height: 175px;
|
||||
}
|
||||
|
||||
@@ -101,6 +101,13 @@ a, .link {
|
||||
}
|
||||
}
|
||||
|
||||
.blended-links a {
|
||||
color: inherit;
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Other HTML Text Elements
|
||||
*/
|
||||
|
||||
@@ -59,4 +59,5 @@ $text-light: #EEE;
|
||||
// Shadows
|
||||
$bs-light: 0 0 4px 1px #CCC;
|
||||
$bs-med: 0 1px 3px 1px rgba(76, 76, 76, 0.26);
|
||||
$bs-card: 0 1px 3px 1px rgba(76, 76, 76, 0.26), 0 1px 12px 0px rgba(76, 76, 76, 0.2);
|
||||
$bs-hover: 0 2px 2px 1px rgba(0,0,0,.13);
|
||||
@@ -154,6 +154,7 @@ $btt-size: 40px;
|
||||
}
|
||||
input {
|
||||
flex: 5;
|
||||
padding: $-xs $-s;
|
||||
&:focus, &:active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
@@ -8,34 +8,34 @@ return [
|
||||
*/
|
||||
|
||||
// Pages
|
||||
'page_create' => 'hat Seite erstellt:',
|
||||
'page_create_notification' => 'hat Seite erfolgreich erstellt:',
|
||||
'page_update' => 'hat Seite aktualisiert:',
|
||||
'page_update_notification' => 'hat Seite erfolgreich aktualisiert:',
|
||||
'page_delete' => 'hat Seite gelöscht:',
|
||||
'page_delete_notification' => 'hat Seite erfolgreich gelöscht:',
|
||||
'page_restore' => 'hat Seite wiederhergstellt:',
|
||||
'page_restore_notification' => 'hat Seite erfolgreich wiederhergstellt:',
|
||||
'page_move' => 'hat Seite verschoben:',
|
||||
'page_create' => 'Seite erstellt',
|
||||
'page_create_notification' => 'Die Seite wurde erfolgreich erstellt.',
|
||||
'page_update' => 'Seite aktualisiert',
|
||||
'page_update_notification' => 'Die Seite wurde erfolgreich aktualisiert.',
|
||||
'page_delete' => 'Seite gelöscht',
|
||||
'page_delete_notification' => 'Die Seite wurde erfolgreich gelöscht.',
|
||||
'page_restore' => 'Seite wiederhergstellt',
|
||||
'page_restore_notification' => 'Die Seite wurde erfolgreich wiederhergstellt.',
|
||||
'page_move' => 'Seite verschoben',
|
||||
|
||||
// Chapters
|
||||
'chapter_create' => 'hat Kapitel erstellt:',
|
||||
'chapter_create_notification' => 'hat Kapitel erfolgreich erstellt:',
|
||||
'chapter_update' => 'hat Kapitel aktualisiert:',
|
||||
'chapter_update_notification' => 'hat Kapitel erfolgreich aktualisiert:',
|
||||
'chapter_delete' => 'hat Kapitel gelöscht',
|
||||
'chapter_delete_notification' => 'hat Kapitel erfolgreich gelöscht:',
|
||||
'chapter_move' => 'hat Kapitel verschoben:',
|
||||
'chapter_create' => 'Kapitel erstellt',
|
||||
'chapter_create_notification' => 'Das Kapitel wurde erfolgreich erstellt.',
|
||||
'chapter_update' => 'Kapitel aktualisiert',
|
||||
'chapter_update_notification' => 'Das Kapitel wurde erfolgreich aktualisiert.',
|
||||
'chapter_delete' => 'Kapitel gelöscht',
|
||||
'chapter_delete_notification' => 'Das Kapitel wurde erfolgreich gelöscht.',
|
||||
'chapter_move' => 'Kapitel verschoben',
|
||||
|
||||
// Books
|
||||
'book_create' => 'hat Buch erstellt:',
|
||||
'book_create_notification' => 'hat Buch erfolgreich erstellt:',
|
||||
'book_update' => 'hat Buch aktualisiert:',
|
||||
'book_update_notification' => 'hat Buch erfolgreich aktualisiert:',
|
||||
'book_delete' => 'hat Buch gelöscht:',
|
||||
'book_delete_notification' => 'hat Buch erfolgreich gelöscht:',
|
||||
'book_sort' => 'hat Buch sortiert:',
|
||||
'book_sort_notification' => 'hat Buch erfolgreich neu sortiert:',
|
||||
'book_create' => 'Buch erstellt',
|
||||
'book_create_notification' => 'Das Buch wurde erfolgreich erstellt.',
|
||||
'book_update' => 'Buch aktualisiert',
|
||||
'book_update_notification' => 'Das Buch wurde erfolgreich aktualisiert.',
|
||||
'book_delete' => 'Buch gelöscht',
|
||||
'book_delete_notification' => 'Das Buch wurde erfolgreich gelöscht.',
|
||||
'book_sort' => 'Buch sortiert',
|
||||
'book_sort_notification' => 'Das Buch wurde erfolgreich neu sortiert.',
|
||||
|
||||
// Other
|
||||
'commented_on' => 'kommentierte',
|
||||
|
||||
@@ -31,6 +31,7 @@ return [
|
||||
'edit' => 'Bearbeiten',
|
||||
'sort' => 'Sortieren',
|
||||
'move' => 'Verschieben',
|
||||
'copy' => 'Kopieren',
|
||||
'reply' => 'Antworten',
|
||||
'delete' => 'Löschen',
|
||||
'search' => 'Suchen',
|
||||
|
||||
@@ -12,7 +12,8 @@ return [
|
||||
'image_uploaded' => 'Hochgeladen am :uploadedDate',
|
||||
'image_load_more' => 'Mehr',
|
||||
'image_image_name' => 'Bildname',
|
||||
'image_delete_confirm' => 'Dieses Bild wird auf den folgenden Seiten benutzt. Bitte klicken Sie erneut auf löschen, wenn Sie dieses Bild wirklich entfernen möchten.',
|
||||
'image_delete_used' => 'Dieses Bild wird auf den folgenden Seiten benutzt. ',
|
||||
'image_delete_confirm' => 'Bitte klicken Sie erneut auf löschen, wenn Sie dieses Bild wirklich entfernen möchten.',
|
||||
'image_select_image' => 'Bild auswählen',
|
||||
'image_dropzone' => 'Ziehen Sie Bilder hierher oder klicken Sie, um ein Bild auszuwählen',
|
||||
'images_deleted' => 'Bilder gelöscht',
|
||||
|
||||
@@ -114,6 +114,9 @@ return [
|
||||
'chapters_move' => 'Kapitel verschieben',
|
||||
'chapters_move_named' => 'Kapitel ":chapterName" verschieben',
|
||||
'chapter_move_success' => 'Das Kapitel wurde in das Buch ":bookName" verschoben.',
|
||||
'pages_copy' => 'Seite kopieren',
|
||||
'pages_copy_desination' => 'Ziel',
|
||||
'pages_copy_success' => 'Seite erfolgreich kopiert',
|
||||
'chapters_permissions' => 'Kapitel-Berechtigungen',
|
||||
'chapters_empty' => 'Aktuell sind keine Kapitel diesem Buch hinzugefügt worden.',
|
||||
'chapters_permissions_active' => 'Kapitel-Berechtigungen aktiv',
|
||||
@@ -176,6 +179,7 @@ return [
|
||||
'pages_revisions_restore' => 'Wiederherstellen',
|
||||
'pages_revisions_none' => 'Diese Seite hat keine älteren Versionen.',
|
||||
'pages_copy_link' => 'Link kopieren',
|
||||
'pages_edit_content_link' => 'Inhalt bearbeiten',
|
||||
'pages_permissions_active' => 'Seiten-Berechtigungen aktiv',
|
||||
'pages_initial_revision' => 'Erste Veröffentlichung',
|
||||
'pages_initial_name' => 'Neue Seite',
|
||||
|
||||
@@ -13,7 +13,8 @@ return [
|
||||
'image_uploaded' => 'Uploaded :uploadedDate',
|
||||
'image_load_more' => 'Load More',
|
||||
'image_image_name' => 'Image Name',
|
||||
'image_delete_confirm' => 'This image is used in the pages below, Click delete again to confirm you want to delete this image.',
|
||||
'image_delete_used' => 'This image is used in the pages below.',
|
||||
'image_delete_confirm' => 'Click delete again to confirm you want to delete this image.',
|
||||
'image_select_image' => 'Select Image',
|
||||
'image_dropzone' => 'Drop images or click here to upload',
|
||||
'images_deleted' => 'Images Deleted',
|
||||
|
||||
@@ -185,6 +185,7 @@ return [
|
||||
'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',
|
||||
|
||||
@@ -34,6 +34,7 @@ return [
|
||||
'app_homepage' => 'Application Homepage',
|
||||
'app_homepage_desc' => 'Select a page to show on the homepage instead of the default view. Page permissions are ignored for selected pages.',
|
||||
'app_homepage_default' => 'Default homepage view chosen',
|
||||
'app_homepage_books' => 'Or select the books page as your homepage. This will override any page selected as your homepage.',
|
||||
'app_disable_comments' => 'Disable comments',
|
||||
'app_disable_comments_desc' => 'Disable comments across all pages in the application. Existing comments are not shown.',
|
||||
|
||||
@@ -50,6 +51,19 @@ return [
|
||||
'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_image_cleanup_ignore_revisions' => 'Ignore images in 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!',
|
||||
|
||||
/**
|
||||
* Role settings
|
||||
*/
|
||||
@@ -68,6 +82,7 @@ return [
|
||||
'role_details' => 'Role Details',
|
||||
'role_name' => 'Role Name',
|
||||
'role_desc' => 'Short Description of Role',
|
||||
'role_external_auth_id' => 'External Authentication IDs',
|
||||
'role_system' => 'System Permissions',
|
||||
'role_manage_users' => 'Manage users',
|
||||
'role_manage_roles' => 'Manage roles & role permissions',
|
||||
|
||||
@@ -35,6 +35,8 @@ return [
|
||||
'book_delete' => 'libro borrado',
|
||||
'book_delete_notification' => 'Libro borrado exitosamente',
|
||||
'book_sort' => 'libro ordenado',
|
||||
'book_sort_notification' => 'Libro re-ordenado exitosamente',
|
||||
'book_sort_notification' => 'Libro reordenado exitosamente',
|
||||
|
||||
// Other
|
||||
'commented_on' => 'comentada el',
|
||||
];
|
||||
|
||||
@@ -31,6 +31,7 @@ return [
|
||||
'edit' => 'Editar',
|
||||
'sort' => 'Ordenar',
|
||||
'move' => 'Mover',
|
||||
'copy' => 'Copiar',
|
||||
'reply' => 'Responder',
|
||||
'delete' => 'Borrar',
|
||||
'search' => 'Buscar',
|
||||
|
||||
@@ -13,7 +13,8 @@ return [
|
||||
'image_uploaded' => 'Subido el :uploadedDate',
|
||||
'image_load_more' => 'Cargar más',
|
||||
'image_image_name' => 'Nombre de imagen',
|
||||
'image_delete_confirm' => 'Esta imagen está siendo utilizada en las páginas mostradas a continuación, haga click de nuevo para confirmar que quiere borrar esta imagen.',
|
||||
'image_delete_used' => 'Esta imagen está siendo utilizada en las páginas mostradas a continuación.',
|
||||
'image_delete_confirm' => 'Haga click de nuevo para confirmar que quiere borrar esta imagen.',
|
||||
'image_select_image' => 'Seleccionar Imagen',
|
||||
'image_dropzone' => 'Arrastre las imágenes o hacer click aquí para Subir',
|
||||
'images_deleted' => 'Imágenes borradas',
|
||||
|
||||
@@ -61,7 +61,7 @@ return [
|
||||
'search_updated_after' => 'Actualizadas después de',
|
||||
'search_created_before' => 'Creadas antes de',
|
||||
'search_created_after' => 'Creadas después de',
|
||||
'search_set_date' => 'Ajustar Fecha',
|
||||
'search_set_date' => 'fecha',
|
||||
'search_update' => 'Actualizar Búsqueda',
|
||||
|
||||
/**
|
||||
@@ -166,6 +166,9 @@ return [
|
||||
'pages_not_in_chapter' => 'La página no está en un capítulo',
|
||||
'pages_move' => 'Mover página',
|
||||
'pages_move_success' => 'Página movida a ":parentName"',
|
||||
'pages_copy' => 'Copiar página',
|
||||
'pages_copy_desination' => 'Destino de la copia',
|
||||
'pages_copy_success' => 'Página copiada a correctamente',
|
||||
'pages_permissions' => 'Permisos de página',
|
||||
'pages_permissions_success' => 'Permisos de página actualizados',
|
||||
'pages_revision' => 'Revisión',
|
||||
@@ -182,6 +185,7 @@ return [
|
||||
'pages_revisions_restore' => 'Restaurar',
|
||||
'pages_revisions_none' => 'Esta página no tiene revisiones',
|
||||
'pages_copy_link' => 'Copiar Enlace',
|
||||
'pages_edit_content_link' => 'Contenido editado',
|
||||
'pages_permissions_active' => 'Permisos de página activos',
|
||||
'pages_initial_revision' => 'Publicación inicial',
|
||||
'pages_initial_name' => 'Página nueva',
|
||||
|
||||
@@ -7,7 +7,7 @@ return [
|
||||
*/
|
||||
|
||||
// Permissions
|
||||
'permission' => 'UNo tiene permisos para visualizar la página solicitada.',
|
||||
'permission' => 'No tiene permisos para visualizar la página solicitada.',
|
||||
'permissionJson' => 'No tiene permisos para ejecutar la acción solicitada.',
|
||||
|
||||
// Auth
|
||||
@@ -65,7 +65,7 @@ return [
|
||||
'role_system_cannot_be_deleted' => 'Este rol es un rol de sistema y no puede ser borrado',
|
||||
'role_registration_default_cannot_delete' => 'Este rol no puede ser borrado mientras sea el rol por defecto de nuevos registros',
|
||||
|
||||
// Comments
|
||||
// Comments
|
||||
'comment_list' => 'Se ha producido un error al buscar los comentarios.',
|
||||
'cannot_add_comment_to_draft' => 'No puedes añadir comentarios a un borrador.',
|
||||
'comment_add' => 'Se ha producido un error al añadir el comentario.',
|
||||
|
||||
@@ -34,6 +34,7 @@ return [
|
||||
'app_homepage' => 'Página de inicio',
|
||||
'app_homepage_desc' => 'Elija la página que se mostrará al inicio en lugar de la vista predeterminada. Se ignorarán los permisos de la página seleccionada.',
|
||||
'app_homepage_default' => 'Página de inicio seleccionada',
|
||||
'app_homepage_books' => 'O selecciona la página de libros como página de inicio. Esto prevalecerá sobre cualquier página seleccionada como página de inicio.',
|
||||
'app_disable_comments' => 'Deshabilitar comentarios',
|
||||
'app_disable_comments_desc' => 'Deshabilita los comentarios en todas las páginas de la aplicación. Los comentarios existentes no se muestran.',
|
||||
|
||||
@@ -50,6 +51,19 @@ return [
|
||||
'reg_confirm_restrict_domain_desc' => 'Introduzca una lista separada por comas de los dominio a los que les gustaría restringir el registro de usuarios. A los usuarios les será enviado un correo electrónico para confirmar la dirección antes de que se le permita interactuar con la aplicación. <br> Tenga en cuenta que los usuarios podrán cambiar sus direcciones de correo electrónico después de registrarse exitosamente.',
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Ninguna restricción establecida',
|
||||
|
||||
/**
|
||||
* Maintenance settings
|
||||
*/
|
||||
|
||||
'maint' => 'Mantenimiento',
|
||||
'maint_image_cleanup' => 'Limpiar imágenes',
|
||||
'maint_image_cleanup_desc' => "Analiza las páginas y sus revisiones para comprobar qué imágenes y dibujos están siendo utilizadas y cuales no son necesarias. Asegúrate de crear una copia completa de la base de datos y de las imágenes antes de lanzar esta opción.",
|
||||
'maint_image_cleanup_ignore_revisions' => 'Ignorar imágenes en revisiones',
|
||||
'maint_image_cleanup_run' => 'Lanzar limpieza',
|
||||
'maint_image_cleanup_warning' => 'Se han encontrado :count imágenes posiblemente no utilizadas . ¿Estás seguro de querer borrar estas imágenes?',
|
||||
'maint_image_cleanup_success' => '¡Se han encontrado y borrado :count imágenes posiblemente no utilizadas!',
|
||||
'maint_image_cleanup_nothing_found' => '¡No se han encontrado imágenes sin utilizar, no se han borrado imágenes!',
|
||||
|
||||
/**
|
||||
* Role settings
|
||||
*/
|
||||
@@ -68,6 +82,7 @@ return [
|
||||
'role_details' => 'Detalles de rol',
|
||||
'role_name' => 'Nombre de rol',
|
||||
'role_desc' => 'Descripción corta de rol',
|
||||
'role_external_auth_id' => 'ID externo de autenticación',
|
||||
'role_system' => 'Permisos de sistema',
|
||||
'role_manage_users' => 'Gestionar usuarios',
|
||||
'role_manage_roles' => 'Gestionar roles y permisos de roles',
|
||||
|
||||
@@ -13,7 +13,8 @@ return [
|
||||
'image_uploaded' => 'Subido el :uploadedDate',
|
||||
'image_load_more' => 'Cargar más',
|
||||
'image_image_name' => 'Nombre de imagen',
|
||||
'image_delete_confirm' => 'Esta imagen esta siendo utilizada en las páginas a continuación, haga click de nuevo para confirmar que quiere borrar esta imagen.',
|
||||
'image_delete_used' => 'Esta imagen esta siendo utilizada en las páginas a continuación.',
|
||||
'image_delete_confirm' => 'Haga click de nuevo para confirmar que quiere borrar esta imagen.',
|
||||
'image_select_image' => 'Seleccionar Imagen',
|
||||
'image_dropzone' => 'Arrastre las imágenes o hacer click aquí para Subir',
|
||||
'images_deleted' => 'Imágenes borradas',
|
||||
|
||||
@@ -124,7 +124,7 @@ return [
|
||||
'chapters_permissions_active' => 'Permisos de capítulo activado',
|
||||
'chapters_permissions_success' => 'Permisos de capítulo actualizados',
|
||||
'chapters_search_this' => 'Buscar en este capítulo',
|
||||
|
||||
|
||||
/**
|
||||
* Pages
|
||||
*/
|
||||
@@ -185,6 +185,7 @@ return [
|
||||
'pages_revisions_restore' => 'Restaurar',
|
||||
'pages_revisions_none' => 'Esta página no tiene revisiones',
|
||||
'pages_copy_link' => 'Copiar enlace',
|
||||
'pages_edit_content_link' => 'Contenido editado',
|
||||
'pages_permissions_active' => 'Permisos de página activos',
|
||||
'pages_initial_revision' => 'Publicación inicial',
|
||||
'pages_initial_name' => 'Página nueva',
|
||||
|
||||
@@ -34,10 +34,10 @@ return [
|
||||
'app_homepage' => 'Página de inicio de la Aplicación',
|
||||
'app_homepage_desc' => 'Seleccione una página de inicio para mostrar en lugar de la vista por defecto. Se ignoran los permisos de página para las páginas seleccionadas.',
|
||||
'app_homepage_default' => 'Página de inicio por defecto seleccionadad',
|
||||
'app_homepage_books' => 'O seleccione la página de libros como su página de inicio. Esto tendrá preferencia sobre cualquier página seleccionada como página de inicio.',
|
||||
'app_disable_comments' => 'Deshabilitar comentarios',
|
||||
'app_disable_comments_desc' => 'Deshabilitar comentarios en todas las páginas de la aplicación. Los comentarios existentes no se muestran.',
|
||||
|
||||
|
||||
/**
|
||||
* Registration settings
|
||||
*/
|
||||
@@ -51,6 +51,19 @@ return [
|
||||
'reg_confirm_restrict_domain_desc' => 'Introduzca una lista separada por comas de los correos electrónicos del dominio a los que les gustaría restringir el registro por dominio. A los usuarios les será enviado un correo elctrónico para confirmar la dirección antes de que se le permita interactuar con la aplicación. <br> Note que a los usuarios se les permitirá cambiar sus direcciones de correo electrónico luego de un registro éxioso.',
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Ninguna restricción establecida',
|
||||
|
||||
/**
|
||||
* Maintenance settings
|
||||
*/
|
||||
|
||||
'maint' => 'Mantenimiento',
|
||||
'maint_image_cleanup' => 'Limpiar imágenes',
|
||||
'maint_image_cleanup_desc' => "Analizar contenido de páginas y revisiones para detectar cuáles imágenes y dibujos están en uso y cuáles son redundantes. Asegúrese de crear un respaldo completo de imágenes y base de datos antes de ejecutar esta tarea.",
|
||||
'maint_image_cleanup_ignore_revisions' => 'Ignorar imágenes en revisión',
|
||||
'maint_image_cleanup_run' => 'Ejecutar limpieza',
|
||||
'maint_image_cleanup_warning' => 'Se encontraron :count imágenes pontencialmente sin uso. Está seguro de que quiere eliminarlas?',
|
||||
'maint_image_cleanup_success' => 'Se encontraron y se eliminaron :count imágenes pontencialmente sin uso!',
|
||||
'maint_image_cleanup_nothing_found' => 'No se encotraron imágenes sin usar, Nada eliminado!',
|
||||
|
||||
/**
|
||||
* Role settings
|
||||
*/
|
||||
@@ -69,6 +82,7 @@ return [
|
||||
'role_details' => 'Detalles de rol',
|
||||
'role_name' => 'Nombre de rol',
|
||||
'role_desc' => 'Descripción corta de rol',
|
||||
'role_external_auth_id' => 'IDs de Autenticación Externa',
|
||||
'role_system' => 'Permisos de sistema',
|
||||
'role_manage_users' => 'Gestionar usuarios',
|
||||
'role_manage_roles' => 'Gestionar roles y permisos de roles',
|
||||
|
||||
@@ -15,7 +15,7 @@ return [
|
||||
'page_delete' => 'a supprimé la page',
|
||||
'page_delete_notification' => 'Page supprimée avec succès',
|
||||
'page_restore' => 'a restauré la page',
|
||||
'page_restore_notification' => 'Page réstaurée avec succès',
|
||||
'page_restore_notification' => 'Page restaurée avec succès',
|
||||
'page_move' => 'a déplacé la page',
|
||||
|
||||
// Chapters
|
||||
@@ -39,5 +39,4 @@ return [
|
||||
|
||||
// Other
|
||||
'commented_on' => 'a commenté'
|
||||
|
||||
];
|
||||
|
||||
@@ -73,4 +73,4 @@ return [
|
||||
'email_not_confirmed_click_link' => 'Merci de cliquer sur le lien dans l\'e-mail qui vous a été envoyé après l\'enregistrement.',
|
||||
'email_not_confirmed_resend' => 'Si vous ne retrouvez plus l\'e-mail, vous pouvez renvoyer un e-mail de confirmation en utilisant le formulaire ci-dessous.',
|
||||
'email_not_confirmed_resend_button' => 'Renvoyez l\'e-mail de confirmation',
|
||||
];
|
||||
];
|
||||
@@ -20,6 +20,7 @@ return [
|
||||
'role' => 'Rôle',
|
||||
'cover_image' => 'Image de couverture',
|
||||
'cover_image_description' => 'Cette image doit être environ 300x170px.',
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
@@ -30,6 +31,7 @@ return [
|
||||
'edit' => 'Editer',
|
||||
'sort' => 'Trier',
|
||||
'move' => 'Déplacer',
|
||||
'copy' => 'Copier',
|
||||
'reply' => 'Répondre',
|
||||
'delete' => 'Supprimer',
|
||||
'search' => 'Chercher',
|
||||
@@ -38,7 +40,6 @@ return [
|
||||
'remove' => 'Enlever',
|
||||
'add' => 'Ajouter',
|
||||
|
||||
|
||||
/**
|
||||
* Misc
|
||||
*/
|
||||
@@ -63,4 +64,4 @@ return [
|
||||
*/
|
||||
'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer sur le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur :',
|
||||
'email_rights' => 'Tous droits réservés',
|
||||
];
|
||||
];
|
||||
@@ -4,7 +4,7 @@ return [
|
||||
/**
|
||||
* Image Manager
|
||||
*/
|
||||
'image_select' => 'Selectionner une image',
|
||||
'image_select' => 'Sélectionner une image',
|
||||
'image_all' => 'Toutes',
|
||||
'image_all_title' => 'Voir toutes les images',
|
||||
'image_book_title' => 'Voir les images ajoutées à ce livre',
|
||||
@@ -13,20 +13,22 @@ return [
|
||||
'image_uploaded' => 'Ajoutée le :uploadedDate',
|
||||
'image_load_more' => 'Charger plus',
|
||||
'image_image_name' => 'Nom de l\'image',
|
||||
'image_delete_confirm' => 'Cette image est utilisée dans les pages ci-dessous. Confirmez que vous souhaitez bien supprimer cette image.',
|
||||
'image_select_image' => 'Selectionner l\'image',
|
||||
'image_delete_used' => 'Cette image est utilisée dans les pages ci-dessous.',
|
||||
'image_delete_confirm' => 'Confirmez que vous souhaitez bien supprimer cette image.',
|
||||
'image_select_image' => 'Sélectionner l\'image',
|
||||
'image_dropzone' => 'Glissez les images ici ou cliquez pour les ajouter',
|
||||
'images_deleted' => 'Images supprimées',
|
||||
'image_preview' => 'Prévisualiser l\'image',
|
||||
'image_upload_success' => 'Image ajoutée avec succès',
|
||||
'image_update_success' => 'Détails de l\'image mis à jour',
|
||||
'image_delete_success' => 'Image supprimée avec succès',
|
||||
'image_upload_remove' => 'Supprimer',
|
||||
|
||||
/**
|
||||
* Code editor
|
||||
*/
|
||||
'code_editor' => 'Editer le code',
|
||||
'code_language' => 'Language du code',
|
||||
'code_language' => 'Langage du code',
|
||||
'code_content' => 'Contenu du code',
|
||||
'code_save' => 'Enregistrer le code',
|
||||
];
|
||||
|
||||
@@ -19,7 +19,6 @@ return [
|
||||
'meta_created_name' => 'Créé :timeLength par :user',
|
||||
'meta_updated' => 'Mis à jour :timeLength',
|
||||
'meta_updated_name' => 'Mis à jour :timeLength par :user',
|
||||
'x_pages' => ':count pages',
|
||||
'entity_select' => 'Sélectionner l\'entité',
|
||||
'images' => 'Images',
|
||||
'my_recent_drafts' => 'Mes brouillons récents',
|
||||
@@ -36,7 +35,7 @@ return [
|
||||
* Permissions and restrictions
|
||||
*/
|
||||
'permissions' => 'Permissions',
|
||||
'permissions_intro' => 'Une fois activées ces permission prendont la priorité sur tous les sets de permissions pré-existants.',
|
||||
'permissions_intro' => 'Une fois activées ces permissions prendront la priorité sur tous les sets de permissions préexistants.',
|
||||
'permissions_enable' => 'Activer les permissions personnalisées',
|
||||
'permissions_save' => 'Enregistrer les permissions',
|
||||
|
||||
@@ -131,6 +130,7 @@ return [
|
||||
*/
|
||||
'page' => 'Page',
|
||||
'pages' => 'Pages',
|
||||
'x_pages' => ':count Page|:count Pages',
|
||||
'pages_popular' => 'Pages populaires',
|
||||
'pages_new' => 'Nouvelle page',
|
||||
'pages_attachments' => 'Fichiers joints',
|
||||
@@ -166,6 +166,9 @@ return [
|
||||
'pages_not_in_chapter' => 'La page n\'est pas dans un chapitre',
|
||||
'pages_move' => 'Déplacer la page',
|
||||
'pages_move_success' => 'Page déplacée à ":parentName"',
|
||||
'pages_copy' => 'Copier la page',
|
||||
'pages_copy_desination' => 'Destination de la copie',
|
||||
'pages_copy_success' => 'Page copiée avec succès',
|
||||
'pages_permissions' => 'Permissions de la page',
|
||||
'pages_permissions_success' => 'Permissions de la page mises à jour',
|
||||
'pages_revision' => 'Révision',
|
||||
@@ -182,6 +185,7 @@ return [
|
||||
'pages_revisions_restore' => 'Restaurer',
|
||||
'pages_revisions_none' => 'Cette page n\'a aucune révision',
|
||||
'pages_copy_link' => 'Copier le lien',
|
||||
'pages_edit_content_link' => 'Modifier le contenu',
|
||||
'pages_permissions_active' => 'Permissions de page actives',
|
||||
'pages_initial_revision' => 'Publication initiale',
|
||||
'pages_initial_name' => 'Nouvelle page',
|
||||
@@ -200,10 +204,12 @@ return [
|
||||
* Editor sidebar
|
||||
*/
|
||||
'page_tags' => 'Mots-clés de la page',
|
||||
'chapter_tags' => 'Mots-clés du chapitre',
|
||||
'book_tags' => 'Mots-clés du livre',
|
||||
'tag' => 'Mot-clé',
|
||||
'tags' => 'Mots-clé',
|
||||
'tags' => 'Mots-clés',
|
||||
'tag_value' => 'Valeur du mot-clé (Optionnel)',
|
||||
'tags_explain' => "Ajouter des mot-clés pour catégoriser votre contenu.",
|
||||
'tags_explain' => "Ajouter des mots-clés pour catégoriser votre contenu.",
|
||||
'tags_add' => 'Ajouter un autre mot-clé',
|
||||
'attachments' => 'Fichiers joints',
|
||||
'attachments_explain' => 'Ajouter des fichiers ou des liens pour les afficher sur votre page. Ils seront affichés dans la barre latérale',
|
||||
@@ -257,6 +263,6 @@ return [
|
||||
'comment_deleted_success' => 'Commentaire supprimé',
|
||||
'comment_created_success' => 'Commentaire ajouté',
|
||||
'comment_updated_success' => 'Commentaire mis à jour',
|
||||
'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire?',
|
||||
'comment_delete_confirm' => 'Etes-vous sûr de vouloir supprimer ce commentaire ?',
|
||||
'comment_in_reply_to' => 'En réponse à :commentId',
|
||||
];
|
||||
];
|
||||
@@ -14,12 +14,13 @@ return [
|
||||
'error_user_exists_different_creds' => 'Un utilisateur avec l\'adresse :email existe déjà.',
|
||||
'email_already_confirmed' => 'Cet e-mail a déjà été validé, vous pouvez vous connecter.',
|
||||
'email_confirmation_invalid' => 'Cette confirmation est invalide. Veuillez essayer de vous inscrire à nouveau.',
|
||||
'email_confirmation_expired' => 'Le jeton de confirmation est perimé. Un nouvel e-mail vous a été envoyé.',
|
||||
'email_confirmation_expired' => 'Le jeton de confirmation est périmé. Un nouvel e-mail vous a été envoyé.',
|
||||
'ldap_fail_anonymous' => 'L\'accès LDAP anonyme n\'a pas abouti',
|
||||
'ldap_fail_authed' => 'L\'accès LDAP n\'a pas abouti avec cet utilisateur et ce mot de passe',
|
||||
'ldap_extension_not_installed' => 'L\'extention LDAP PHP n\'est pas installée',
|
||||
'ldap_extension_not_installed' => 'L\'extension LDAP PHP n\'est pas installée',
|
||||
'ldap_cannot_connect' => 'Impossible de se connecter au serveur LDAP, la connexion initiale a échoué',
|
||||
'social_no_action_defined' => 'Pas d\'action définie',
|
||||
'social_login_bad_response' => "Erreur pendant la tentative de connexion à :socialAccount : \n:error",
|
||||
'social_account_in_use' => 'Ce compte :socialAccount est déjà utilisé. Essayez de vous connecter via :socialAccount.',
|
||||
'social_account_email_in_use' => 'L\'email :email est déjà utilisé. Si vous avez déjà un compte :socialAccount, vous pouvez le joindre à votre profil existant.',
|
||||
'social_account_existing' => 'Ce compte :socialAccount est déjà rattaché à votre profil.',
|
||||
@@ -34,13 +35,17 @@ return [
|
||||
'cannot_get_image_from_url' => 'Impossible de récupérer l\'image depuis :url',
|
||||
'cannot_create_thumbs' => 'Le serveur ne peut pas créer de miniature, vérifier que l\'extension PHP GD est installée.',
|
||||
'server_upload_limit' => 'La taille du fichier est trop grande.',
|
||||
'uploaded' => 'Le serveur n\'autorise pas l\'envoi d\'un fichier de cette taille. Veuillez essayer avec une taille de fichier réduite.',
|
||||
'image_upload_error' => 'Une erreur est survenue pendant l\'envoi de l\'image',
|
||||
'image_upload_type_error' => 'LE format de l\'image envoyée n\'est pas valide',
|
||||
|
||||
// Attachments
|
||||
'attachment_page_mismatch' => 'Page mismatch during attachment update',
|
||||
'attachment_page_mismatch' => 'Page incorrecte durant la mise à jour du fichier joint',
|
||||
'attachment_not_found' => 'Fichier joint non trouvé',
|
||||
|
||||
// Pages
|
||||
'page_draft_autosave_fail' => 'Le brouillon n\'a pas pu être sauvé. Vérifiez votre connexion internet',
|
||||
'page_custom_home_deletion' => 'Impossible de supprimer une page définie comme page d\'accueil',
|
||||
|
||||
// Entities
|
||||
'entity_not_found' => 'Entité non trouvée',
|
||||
|
||||
@@ -34,8 +34,10 @@ return [
|
||||
'app_homepage' => 'Page d\'accueil de l\'application',
|
||||
'app_homepage_desc' => 'Choisissez une page à afficher sur la page d\'accueil au lieu de la vue par défaut. Les permissions sont ignorées pour les pages sélectionnées.',
|
||||
'app_homepage_default' => 'Page d\'accueil par défaut sélectionnée',
|
||||
'app_homepage_books' => 'Ou sélectionner la page des livres comme page d\'accueil. Cela va ignorer la page séléctionnée comme page d\'accueil.',
|
||||
'app_disable_comments' => 'Désactiver les commentaires',
|
||||
'app_disable_comments_desc' => 'Désactive les commentaires sur toutes les pages de l\'application. Les commentaires existants ne sont pas affichés.',
|
||||
|
||||
/**
|
||||
* Registration settings
|
||||
*/
|
||||
@@ -46,9 +48,22 @@ return [
|
||||
'reg_confirm_email' => 'Obliger la confirmation par e-mail ?',
|
||||
'reg_confirm_email_desc' => 'Si la restriction de domaine est activée, la confirmation sera automatiquement obligatoire et cette valeur sera ignorée.',
|
||||
'reg_confirm_restrict_domain' => 'Restreindre l\'inscription à un domaine',
|
||||
'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les utilisateur recevront un e-mail de confirmation à cette adresse. <br> Les utilisateurs pourront changer leur adresse après inscription s\'ils le souhaitent.',
|
||||
'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les utilisateurs recevront un e-mail de confirmation à cette adresse. <br> Les utilisateurs pourront changer leur adresse après inscription s\'ils le souhaitent.',
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Aucune restriction en place',
|
||||
|
||||
/**
|
||||
* Maintenance settings
|
||||
*/
|
||||
|
||||
'maint' => 'Maintenance',
|
||||
'maint_image_cleanup' => 'Nettoyer les images',
|
||||
'maint_image_cleanup_desc' => "Scan le contenu des pages et des révisions pour vérifier les images et les dessins en cours d'utilisation et lesquels sont redondant. Veuillez à faire une sauvegarde de la base de données et des images avant de lancer ceci.",
|
||||
'maint_image_cleanup_ignore_revisions' => 'Ignorer les images dans les révisions',
|
||||
'maint_image_cleanup_run' => 'Lancer le nettoyage',
|
||||
'maint_image_cleanup_warning' => ':count images potentiellement inutilisées trouvées. Etes-vous sûr de vouloir supprimer ces images ?',
|
||||
'maint_image_cleanup_success' => ':count images potentiellement inutilisées trouvées et supprimées !',
|
||||
'maint_image_cleanup_nothing_found' => 'Aucune image inutilisée trouvée, rien à supprimer !',
|
||||
|
||||
/**
|
||||
* Role settings
|
||||
*/
|
||||
@@ -61,23 +76,24 @@ return [
|
||||
'role_delete_confirm' => 'Ceci va supprimer le rôle \':roleName\'.',
|
||||
'role_delete_users_assigned' => 'Ce rôle a :userCount utilisateurs assignés. Vous pouvez choisir un rôle de remplacement pour ces utilisateurs.',
|
||||
'role_delete_no_migration' => "Ne pas assigner de nouveau rôle",
|
||||
'role_delete_sure' => 'Êtes vous sûr(e) de vouloir supprimer ce rôle ?',
|
||||
'role_delete_sure' => 'Êtes-vous sûr de vouloir supprimer ce rôle ?',
|
||||
'role_delete_success' => 'Le rôle a été supprimé avec succès',
|
||||
'role_edit' => 'Modifier le rôle',
|
||||
'role_details' => 'Détails du rôle',
|
||||
'role_name' => 'Nom du rôle',
|
||||
'role_desc' => 'Courte description du rôle',
|
||||
'role_external_auth_id' => 'Identifiants d\'authentification externes',
|
||||
'role_system' => 'Permissions système',
|
||||
'role_manage_users' => 'Gérer les utilisateurs',
|
||||
'role_manage_roles' => 'Gérer les rôles et permissions',
|
||||
'role_manage_entity_permissions' => 'Gérer les permissions sur les livres, chapitres et pages',
|
||||
'role_manage_own_entity_permissions' => 'Gérer les permissions de ses propres livres, chapitres, et pages',
|
||||
'role_manage_settings' => 'Gérer les préférences de l\'application',
|
||||
'role_asset' => 'Asset Permissions',
|
||||
'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' => 'Permissions des ressources',
|
||||
'role_asset_desc' => 'Ces permissions contrôlent l\'accès par défaut des ressources dans le système. Les permissions dans les livres, les chapitres et les pages ignoreront ces permissions',
|
||||
'role_all' => 'Tous',
|
||||
'role_own' => 'Propres',
|
||||
'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
|
||||
'role_controlled_by_asset' => 'Contrôlé par les ressources les ayant envoyés',
|
||||
'role_save' => 'Enregistrer le rôle',
|
||||
'role_update_success' => 'Rôle mis à jour avec succès',
|
||||
'role_users' => 'Utilisateurs ayant ce rôle',
|
||||
@@ -93,7 +109,7 @@ return [
|
||||
'users_search' => 'Chercher les utilisateurs',
|
||||
'users_role' => 'Rôles des utilisateurs',
|
||||
'users_external_auth_id' => 'Identifiant d\'authentification externe',
|
||||
'users_password_warning' => 'Remplissez ce fomulaire uniquement si vous souhaitez changer de mot de passe:',
|
||||
'users_password_warning' => 'Remplissez ce formulaire uniquement si vous souhaitez changer de mot de passe:',
|
||||
'users_system_public' => 'Cet utilisateur représente les invités visitant votre instance. Il est assigné automatiquement aux invités.',
|
||||
'users_books_view_type' => 'Disposition d\'affichage préférée pour les livres',
|
||||
'users_delete' => 'Supprimer un utilisateur',
|
||||
|
||||
@@ -13,7 +13,8 @@ return [
|
||||
'image_uploaded' => 'Uploaded :uploadedDate',
|
||||
'image_load_more' => 'Carica Altre',
|
||||
'image_image_name' => 'Nome Immagine',
|
||||
'image_delete_confirm' => 'Questa immagine è usata nelle pagine elencate, clicca elimina nuovamente per confermare.',
|
||||
'image_delete_used' => 'Questa immagine è usata nelle pagine elencate.',
|
||||
'image_delete_confirm' => 'Clicca elimina nuovamente per confermare.',
|
||||
'image_select_image' => 'Seleziona Immagine',
|
||||
'image_dropzone' => 'Rilascia immagini o clicca qui per caricarle',
|
||||
'images_deleted' => 'Immagini Eliminate',
|
||||
|
||||
@@ -182,6 +182,7 @@ return [
|
||||
'pages_revisions_restore' => 'Ripristina',
|
||||
'pages_revisions_none' => 'Questa pagina non ha versioni',
|
||||
'pages_copy_link' => 'Copia Link',
|
||||
'pages_edit_content_link' => 'Modifica contenuto',
|
||||
'pages_permissions_active' => 'Permessi Pagina Attivi',
|
||||
'pages_initial_revision' => 'Pubblicazione iniziale',
|
||||
'pages_initial_name' => 'Nuova Pagina',
|
||||
|
||||
@@ -13,7 +13,8 @@ return [
|
||||
'image_uploaded' => 'アップロード日時: :uploadedDate',
|
||||
'image_load_more' => 'さらに読み込む',
|
||||
'image_image_name' => '画像名',
|
||||
'image_delete_confirm' => 'この画像は以下のページで利用されています。削除してもよろしければ、再度ボタンを押して下さい。',
|
||||
'image_delete_used' => 'この画像は以下のページで利用されています。',
|
||||
'image_delete_confirm' => '削除してもよろしければ、再度ボタンを押して下さい。',
|
||||
'image_select_image' => '選択',
|
||||
'image_dropzone' => '画像をドロップするか、クリックしてアップロード',
|
||||
'images_deleted' => '画像を削除しました',
|
||||
|
||||
@@ -179,6 +179,7 @@ return [
|
||||
'pages_revisions_restore' => '復元',
|
||||
'pages_revisions_none' => 'このページにはリビジョンがありません',
|
||||
'pages_copy_link' => 'リンクをコピー',
|
||||
'pages_edit_content_link' => 'コンテンツの編集',
|
||||
'pages_permissions_active' => 'ページの権限は有効です',
|
||||
'pages_initial_revision' => '初回の公開',
|
||||
'pages_initial_name' => '新規ページ',
|
||||
|
||||
@@ -13,7 +13,8 @@ return [
|
||||
'image_uploaded' => 'Uploaded :uploadedDate',
|
||||
'image_load_more' => 'Meer Laden',
|
||||
'image_image_name' => 'Afbeeldingsnaam',
|
||||
'image_delete_confirm' => 'Deze afbeeldingen is op onderstaande pagina\'s in gebruik, Klik opnieuw op verwijderen om de afbeelding echt te verwijderen.',
|
||||
'image_delete_used' => 'Deze afbeeldingen is op onderstaande pagina\'s in gebruik.',
|
||||
'image_delete_confirm' => 'Klik opnieuw op verwijderen om de afbeelding echt te verwijderen.',
|
||||
'image_select_image' => 'Kies Afbeelding',
|
||||
'image_dropzone' => 'Sleep afbeeldingen hier of klik hier om te uploaden',
|
||||
'images_deleted' => 'Verwijderde Afbeeldingen',
|
||||
|
||||
@@ -14,7 +14,7 @@ return [
|
||||
'recent_activity' => 'Recente Activiteit',
|
||||
'create_now' => 'Maak er zelf één',
|
||||
'revisions' => 'Revisies',
|
||||
'meta_revision' => 'Revisie #:revisionCount',
|
||||
'meta_revision' => 'Revisie #:revisionCount',
|
||||
'meta_created' => 'Aangemaakt :timeLength',
|
||||
'meta_created_name' => 'Aangemaakt: :timeLength door :user',
|
||||
'meta_updated' => ':timeLength Aangepast',
|
||||
@@ -44,7 +44,7 @@ return [
|
||||
* Search
|
||||
*/
|
||||
'search_results' => 'Zoekresultaten',
|
||||
'search_total_results_found' => ':count resultaten gevonden|:count resultaten gevonden',
|
||||
'search_total_results_found' => ':count resultaten gevonden|:count resultaten gevonden',
|
||||
'search_clear' => 'Zoekopdracht wissen',
|
||||
'search_no_pages' => 'Er zijn geen pagina\'s gevonden',
|
||||
'search_for_term' => 'Zoeken op :term',
|
||||
@@ -105,7 +105,7 @@ return [
|
||||
*/
|
||||
'chapter' => 'Hoofdstuk',
|
||||
'chapters' => 'Hoofdstukken',
|
||||
'x_chapters' => ':count Hoofdstuk|:count Hoofdstukken',
|
||||
'x_chapters' => ':count Hoofdstuk|:count Hoofdstukken',
|
||||
'chapters_popular' => 'Populaire Hoofdstukken',
|
||||
'chapters_new' => 'Nieuw Hoofdstuk',
|
||||
'chapters_create' => 'Hoofdstuk Toevoegen',
|
||||
@@ -124,14 +124,14 @@ return [
|
||||
'chapters_empty' => 'Er zijn geen pagina\'s in dit hoofdstuk aangemaakt.',
|
||||
'chapters_permissions_active' => 'Hoofdstuk Permissies Actief',
|
||||
'chapters_permissions_success' => 'Hoofdstuk Permissies Bijgewerkt',
|
||||
'chapters_search_this' => 'Doorzoek dit hoofdstuk',
|
||||
'chapters_search_this' => 'Doorzoek dit hoofdstuk',
|
||||
|
||||
/**
|
||||
* Pages
|
||||
*/
|
||||
'page' => 'Pagina',
|
||||
'pages' => 'Pagina\'s',
|
||||
'x_pages' => ':count Pagina|:count Pagina\'s',
|
||||
'x_pages' => ':count Pagina|:count Pagina\'s',
|
||||
'pages_popular' => 'Populaire Pagina\'s',
|
||||
'pages_new' => 'Nieuwe Pagina',
|
||||
'pages_attachments' => 'Bijlages',
|
||||
@@ -168,7 +168,7 @@ return [
|
||||
'pages_move_success' => 'Pagina verplaatst naar ":parentName"',
|
||||
'pages_permissions' => 'Pagina Permissies',
|
||||
'pages_permissions_success' => 'Pagina Permissies bijgwerkt',
|
||||
'pages_revision' => 'Revisie',
|
||||
'pages_revision' => 'Revisie',
|
||||
'pages_revisions' => 'Pagina Revisies',
|
||||
'pages_revisions_named' => 'Pagina Revisies voor :pageName',
|
||||
'pages_revision_named' => 'Pagina Revisie voor :pageName',
|
||||
@@ -182,6 +182,7 @@ return [
|
||||
'pages_revisions_restore' => 'Herstellen',
|
||||
'pages_revisions_none' => 'Deze pagina heeft geen revisies',
|
||||
'pages_copy_link' => 'Link Kopiëren',
|
||||
'pages_edit_content_link' => 'Bewerk inhoud',
|
||||
'pages_permissions_active' => 'Pagina Permissies Actief',
|
||||
'pages_initial_revision' => 'Eerste publicatie',
|
||||
'pages_initial_name' => 'Nieuwe Pagina',
|
||||
|
||||
@@ -13,7 +13,8 @@ return [
|
||||
'image_uploaded' => 'Udostępniono :uploadedDate',
|
||||
'image_load_more' => 'Wczytaj więcej',
|
||||
'image_image_name' => 'Nazwa obrazka',
|
||||
'image_delete_confirm' => 'Ten obrazek jest używany na stronach poniżej, kliknij ponownie Usuń by potwierdzić usunięcie obrazka.',
|
||||
'image_delete_used' => 'Ten obrazek jest używany na stronach poniżej.',
|
||||
'image_delete_confirm' => 'Kliknij ponownie Usuń by potwierdzić usunięcie obrazka.',
|
||||
'image_select_image' => 'Wybierz obrazek',
|
||||
'image_dropzone' => 'Upuść obrazki tutaj lub kliknij by wybrać obrazki do udostępnienia',
|
||||
'images_deleted' => 'Usunięte obrazki',
|
||||
|
||||
@@ -179,6 +179,7 @@ return [
|
||||
'pages_revisions_restore' => 'Przywróć',
|
||||
'pages_revisions_none' => 'Ta strona nie posiada żadnych rewizji',
|
||||
'pages_copy_link' => 'Kopiuj link',
|
||||
'pages_edit_content_link' => 'Edytuj zawartość',
|
||||
'pages_permissions_active' => 'Uprawnienia strony aktywne',
|
||||
'pages_initial_revision' => 'Wydanie pierwotne',
|
||||
'pages_initial_name' => 'Nowa strona',
|
||||
|
||||
@@ -20,6 +20,7 @@ return [
|
||||
'role' => 'Regra',
|
||||
'cover_image' => 'Imagem de capa',
|
||||
'cover_image_description' => 'Esta imagem deve ser aproximadamente 300x170px.',
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
@@ -30,6 +31,7 @@ return [
|
||||
'edit' => 'Editar',
|
||||
'sort' => 'Ordenar',
|
||||
'move' => 'Mover',
|
||||
'copy' => 'Copiar',
|
||||
'reply' => 'Responder',
|
||||
'delete' => 'Excluir',
|
||||
'search' => 'Pesquisar',
|
||||
@@ -48,6 +50,8 @@ return [
|
||||
'toggle_details' => 'Alternar Detalhes',
|
||||
'toggle_thumbnails' => 'Alternar Miniaturas',
|
||||
'details' => 'Detalhes',
|
||||
'grid_view' => 'Visualização em Grade',
|
||||
'list_view' => 'Visualização em Lista',
|
||||
|
||||
/**
|
||||
* Header
|
||||
|
||||
@@ -13,7 +13,8 @@ return [
|
||||
'image_uploaded' => 'Carregado :uploadedDate',
|
||||
'image_load_more' => 'Carregar Mais',
|
||||
'image_image_name' => 'Nome da Imagem',
|
||||
'image_delete_confirm' => 'Essa imagem é usada nas páginas abaixo. Clique em Excluir novamente para confirmar que você deseja mesmo eliminar a imagem.',
|
||||
'image_delete_used' => 'Essa imagem é usada nas páginas abaixo.',
|
||||
'image_delete_confirm' => 'Clique em Excluir novamente para confirmar que você deseja mesmo eliminar a imagem.',
|
||||
'image_select_image' => 'Selecionar Imagem',
|
||||
'image_dropzone' => 'Arraste imagens ou clique aqui para fazer upload',
|
||||
'images_deleted' => 'Imagens excluídas',
|
||||
|
||||
@@ -181,6 +181,7 @@ return [
|
||||
'pages_revisions_restore' => 'Restaurar',
|
||||
'pages_revisions_none' => 'Essa página não tem revisões',
|
||||
'pages_copy_link' => 'Copia Link',
|
||||
'pages_edit_content_link' => 'Editar conteúdo',
|
||||
'pages_permissions_active' => 'Permissões de Página Ativas',
|
||||
'pages_initial_revision' => 'Publicação Inicial',
|
||||
'pages_initial_name' => 'Nova Página',
|
||||
|
||||
@@ -13,7 +13,8 @@ return [
|
||||
'image_uploaded' => 'Загруженно :uploadedDate',
|
||||
'image_load_more' => 'Загрузить ещё',
|
||||
'image_image_name' => 'Имя изображения',
|
||||
'image_delete_confirm' => 'Это изображение используется на странице ниже. Снова кликните удалить для подтверждения того что вы хотите удалить.',
|
||||
'image_delete_used' => 'Это изображение используется на странице ниже.',
|
||||
'image_delete_confirm' => 'Снова кликните удалить для подтверждения того что вы хотите удалить.',
|
||||
'image_select_image' => 'Выбрать изображение',
|
||||
'image_dropzone' => 'Перетащите изображение или кликните для загрузки',
|
||||
'images_deleted' => 'Изображения удалены',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user