Merge pull request #13 from BookStackApp/master
Getting the latest changes.
This commit is contained in:
		
						commit
						4df3267521
					
				| 
						 | 
				
			
			@ -25,4 +25,4 @@ after_failure:
 | 
			
		|||
  - cat storage/logs/laravel.log
 | 
			
		||||
 | 
			
		||||
script:
 | 
			
		||||
  - phpunit
 | 
			
		||||
  - phpunit
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace BookStack\Console\Commands;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Console\Command;
 | 
			
		||||
use Illuminate\Support\Facades\DB;
 | 
			
		||||
 | 
			
		||||
class UpgradeDatabaseEncoding extends Command
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * The name and signature of the console command.
 | 
			
		||||
     *
 | 
			
		||||
     * @var string
 | 
			
		||||
     */
 | 
			
		||||
    protected $signature = 'bookstack:db-utf8mb4 {--database= : The database connection to use.}';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The console command description.
 | 
			
		||||
     *
 | 
			
		||||
     * @var string
 | 
			
		||||
     */
 | 
			
		||||
    protected $description = 'Generate SQL commands to upgrade the database to UTF8mb4';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new command instance.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute the console command.
 | 
			
		||||
     *
 | 
			
		||||
     * @return mixed
 | 
			
		||||
     */
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        $connection = DB::getDefaultConnection();
 | 
			
		||||
        if ($this->option('database') !== null) {
 | 
			
		||||
            DB::setDefaultConnection($this->option('database'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $database = DB::getDatabaseName();
 | 
			
		||||
        $tables = DB::select('SHOW TABLES');
 | 
			
		||||
        $this->line('ALTER DATABASE `'.$database.'` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
 | 
			
		||||
        $this->line('USE `'.$database.'`;');
 | 
			
		||||
        $key = 'Tables_in_' . $database;
 | 
			
		||||
        foreach ($tables as $table) {
 | 
			
		||||
            $tableName = $table->$key;
 | 
			
		||||
            $this->line('ALTER TABLE `'.$tableName.'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        DB::setDefaultConnection($connection);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +15,8 @@ class Kernel extends ConsoleKernel
 | 
			
		|||
        Commands\ClearActivity::class,
 | 
			
		||||
        Commands\ClearRevisions::class,
 | 
			
		||||
        Commands\RegeneratePermissions::class,
 | 
			
		||||
        Commands\RegenerateSearch::class
 | 
			
		||||
        Commands\RegenerateSearch::class,
 | 
			
		||||
        Commands\UpgradeDatabaseEncoding::class
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,9 @@ class AppServiceProvider extends ServiceProvider
 | 
			
		|||
        \Blade::directive('icon', function($expression) {
 | 
			
		||||
            return "<?php echo icon($expression); ?>";
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Allow longer string lengths after upgrade to utf8mb4
 | 
			
		||||
        \Schema::defaultStringLength(191);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -571,7 +571,7 @@ class EntityRepo
 | 
			
		|||
 | 
			
		||||
        $draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id);
 | 
			
		||||
        $draftPage->html = $this->formatHtml($input['html']);
 | 
			
		||||
        $draftPage->text = strip_tags($draftPage->html);
 | 
			
		||||
        $draftPage->text = $this->pageToPlainText($draftPage);
 | 
			
		||||
        $draftPage->draft = false;
 | 
			
		||||
        $draftPage->revision_count = 1;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -713,6 +713,17 @@ class EntityRepo
 | 
			
		|||
        return $content;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the plain text version of a page's content.
 | 
			
		||||
     * @param Page $page
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function pageToPlainText(Page $page)
 | 
			
		||||
    {
 | 
			
		||||
        $html = $this->renderPage($page);
 | 
			
		||||
        return strip_tags($html);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a new draft page instance.
 | 
			
		||||
     * @param Book $book
 | 
			
		||||
| 
						 | 
				
			
			@ -816,7 +827,7 @@ class EntityRepo
 | 
			
		|||
        $userId = user()->id;
 | 
			
		||||
        $page->fill($input);
 | 
			
		||||
        $page->html = $this->formatHtml($input['html']);
 | 
			
		||||
        $page->text = strip_tags($page->html);
 | 
			
		||||
        $page->text = $this->pageToPlainText($page);
 | 
			
		||||
        if (setting('app-editor') !== 'markdown') $page->markdown = '';
 | 
			
		||||
        $page->updated_by = $userId;
 | 
			
		||||
        $page->revision_count++;
 | 
			
		||||
| 
						 | 
				
			
			@ -933,7 +944,7 @@ class EntityRepo
 | 
			
		|||
        $revision = $page->revisions()->where('id', '=', $revisionId)->first();
 | 
			
		||||
        $page->fill($revision->toArray());
 | 
			
		||||
        $page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
 | 
			
		||||
        $page->text = strip_tags($page->html);
 | 
			
		||||
        $page->text = $this->pageToPlainText($page);
 | 
			
		||||
        $page->updated_by = user()->id;
 | 
			
		||||
        $page->save();
 | 
			
		||||
        $this->searchService->indexEntity($page);
 | 
			
		||||
| 
						 | 
				
			
			@ -953,7 +964,7 @@ class EntityRepo
 | 
			
		|||
        if ($page->draft) {
 | 
			
		||||
            $page->fill($data);
 | 
			
		||||
            if (isset($data['html'])) {
 | 
			
		||||
                $page->text = strip_tags($data['html']);
 | 
			
		||||
                $page->text = $this->pageToPlainText($page);
 | 
			
		||||
            }
 | 
			
		||||
            $page->save();
 | 
			
		||||
            return $page;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,6 +42,8 @@ class LdapService
 | 
			
		|||
        $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) return null;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -259,7 +259,7 @@ class PermissionService
 | 
			
		|||
        $roleIds = array_map(function($role) {
 | 
			
		||||
            return $role->id;
 | 
			
		||||
        }, $roles);
 | 
			
		||||
        $this->jointPermission->newQuery()->whereIn('id', $roleIds)->delete();
 | 
			
		||||
        $this->jointPermission->newQuery()->whereIn('role_id', $roleIds)->delete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,7 +58,7 @@ return [
 | 
			
		|||
    */
 | 
			
		||||
 | 
			
		||||
    'locale' => env('APP_LANG', 'en'),
 | 
			
		||||
    'locales' => ['en', 'de', 'es', 'fr', 'nl', 'pt_BR', 'sk'],
 | 
			
		||||
    'locales' => ['en', 'de', 'es', 'fr', 'nl', 'pt_BR', 'sk', 'ja', 'pl'],
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,14 @@ if (env('REDIS_SERVERS', false)) {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$mysql_host = env('DB_HOST', 'localhost');
 | 
			
		||||
$mysql_host_exploded = explode(':', $mysql_host);
 | 
			
		||||
$mysql_port = env('DB_PORT', 3306);
 | 
			
		||||
if (count($mysql_host_exploded) > 1) {
 | 
			
		||||
    $mysql_host = $mysql_host_exploded[0];
 | 
			
		||||
    $mysql_port = intval($mysql_host_exploded[1]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
| 
						 | 
				
			
			@ -70,12 +78,13 @@ return [
 | 
			
		|||
 | 
			
		||||
        'mysql' => [
 | 
			
		||||
            'driver'    => 'mysql',
 | 
			
		||||
            'host'      => env('DB_HOST', 'localhost'),
 | 
			
		||||
            'host'      => $mysql_host,
 | 
			
		||||
            'database'  => env('DB_DATABASE', 'forge'),
 | 
			
		||||
            'username'  => env('DB_USERNAME', 'forge'),
 | 
			
		||||
            'password'  => env('DB_PASSWORD', ''),
 | 
			
		||||
            'charset'   => 'utf8',
 | 
			
		||||
            'collation' => 'utf8_unicode_ci',
 | 
			
		||||
            'port'      => $mysql_port,
 | 
			
		||||
            'charset'   => 'utf8mb4',
 | 
			
		||||
            'collation' => 'utf8mb4_unicode_ci',
 | 
			
		||||
            'prefix'    => '',
 | 
			
		||||
            'strict'    => false,
 | 
			
		||||
        ],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,6 +80,7 @@ return [
 | 
			
		|||
        'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
 | 
			
		||||
        'version' => env('LDAP_VERSION', false),
 | 
			
		||||
        'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
 | 
			
		||||
        'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,112 +0,0 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Facades\Schema;
 | 
			
		||||
use Illuminate\Database\Schema\Blueprint;
 | 
			
		||||
use Illuminate\Database\Migrations\Migration;
 | 
			
		||||
 | 
			
		||||
class CreateCommentsTable extends Migration
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Run the migrations.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function up()
 | 
			
		||||
    {
 | 
			
		||||
        if (Schema::hasTable('comments')) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        Schema::create('comments', function (Blueprint $table) {
 | 
			
		||||
            $table->increments('id')->unsigned();
 | 
			
		||||
            $table->integer('page_id')->unsigned();
 | 
			
		||||
            $table->longText('text')->nullable();
 | 
			
		||||
            $table->longText('html')->nullable();
 | 
			
		||||
            $table->integer('parent_id')->unsigned()->nullable();
 | 
			
		||||
            $table->integer('created_by')->unsigned();
 | 
			
		||||
            $table->integer('updated_by')->unsigned()->nullable();
 | 
			
		||||
            $table->index(['page_id', 'parent_id']);
 | 
			
		||||
            $table->timestamps();
 | 
			
		||||
 | 
			
		||||
            // Get roles with permissions we need to change
 | 
			
		||||
            $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id;
 | 
			
		||||
 | 
			
		||||
            // Create & attach new entity permissions
 | 
			
		||||
            $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
 | 
			
		||||
            $entity = 'Comment';
 | 
			
		||||
            foreach ($ops as $op) {
 | 
			
		||||
                $permissionId = DB::table('role_permissions')->insertGetId([
 | 
			
		||||
                    'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)),
 | 
			
		||||
                    'display_name' => $op . ' ' . $entity . 's',
 | 
			
		||||
                    'created_at' => \Carbon\Carbon::now()->toDateTimeString(),
 | 
			
		||||
                    'updated_at' => \Carbon\Carbon::now()->toDateTimeString()
 | 
			
		||||
                ]);
 | 
			
		||||
                DB::table('permission_role')->insert([
 | 
			
		||||
                    'role_id' => $adminRoleId,
 | 
			
		||||
                    'permission_id' => $permissionId
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get roles with permissions we need to change
 | 
			
		||||
            /*
 | 
			
		||||
            $editorRole = DB::table('roles')->where('name', '=', 'editor')->first();
 | 
			
		||||
            if (!empty($editorRole)) {
 | 
			
		||||
                $editorRoleId = $editorRole->id;
 | 
			
		||||
                // Create & attach new entity permissions
 | 
			
		||||
                $ops = ['Create All', 'Create Own', 'Update Own', 'Delete Own'];
 | 
			
		||||
                $entity = 'Comment';
 | 
			
		||||
                foreach ($ops as $op) {
 | 
			
		||||
                    $permissionId = DB::table('role_permissions')->insertGetId([
 | 
			
		||||
                        'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)),
 | 
			
		||||
                        'display_name' => $op . ' ' . $entity . 's',
 | 
			
		||||
                        'created_at' => \Carbon\Carbon::now()->toDateTimeString(),
 | 
			
		||||
                        'updated_at' => \Carbon\Carbon::now()->toDateTimeString()
 | 
			
		||||
                    ]);
 | 
			
		||||
                    DB::table('permission_role')->insert([
 | 
			
		||||
                        'role_id' => $editorRoleId,
 | 
			
		||||
                        'permission_id' => $permissionId
 | 
			
		||||
                    ]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get roles with permissions we need to change
 | 
			
		||||
            $viewerRole = DB::table('roles')->where('name', '=', 'viewer')->first();
 | 
			
		||||
            if (!empty($viewerRole)) {
 | 
			
		||||
                $viewerRoleId = $viewerRole->id;
 | 
			
		||||
                // Create & attach new entity permissions
 | 
			
		||||
                $ops = ['Create All'];
 | 
			
		||||
                $entity = 'Comment';
 | 
			
		||||
                foreach ($ops as $op) {
 | 
			
		||||
                    $permissionId = DB::table('role_permissions')->insertGetId([
 | 
			
		||||
                        'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)),
 | 
			
		||||
                        'display_name' => $op . ' ' . $entity . 's',
 | 
			
		||||
                        'created_at' => \Carbon\Carbon::now()->toDateTimeString(),
 | 
			
		||||
                        'updated_at' => \Carbon\Carbon::now()->toDateTimeString()
 | 
			
		||||
                    ]);
 | 
			
		||||
                    DB::table('permission_role')->insert([
 | 
			
		||||
                        'role_id' => $viewerRoleId,
 | 
			
		||||
                        'permission_id' => $permissionId
 | 
			
		||||
                    ]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            */
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reverse the migrations.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function down()
 | 
			
		||||
    {
 | 
			
		||||
        Schema::dropIfExists('comments');
 | 
			
		||||
        // Create & attach new entity permissions
 | 
			
		||||
        $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
 | 
			
		||||
        $entity = 'Comment';
 | 
			
		||||
        foreach ($ops as $op) {
 | 
			
		||||
            $permName = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op));
 | 
			
		||||
            DB::table('role_permissions')->where('name', '=', $permName)->delete();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,38 +0,0 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Facades\Schema;
 | 
			
		||||
use Illuminate\Database\Schema\Blueprint;
 | 
			
		||||
use Illuminate\Database\Migrations\Migration;
 | 
			
		||||
 | 
			
		||||
class CommentsAddActiveCol extends Migration
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Run the migrations.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function up()
 | 
			
		||||
    {
 | 
			
		||||
        Schema::table('comments', function (Blueprint $table) {
 | 
			
		||||
            // add column active
 | 
			
		||||
            $table->boolean('active')->default(true);
 | 
			
		||||
            $table->dropIndex('comments_page_id_parent_id_index');
 | 
			
		||||
            $table->index(['page_id']);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reverse the migrations.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function down()
 | 
			
		||||
    {
 | 
			
		||||
        Schema::table('comments', function (Blueprint $table) {
 | 
			
		||||
            // reversing the schema
 | 
			
		||||
            $table->dropIndex('comments_page_id_index');
 | 
			
		||||
            $table->dropColumn('active');
 | 
			
		||||
            $table->index(['page_id', 'parent_id']);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Migrations\Migration;
 | 
			
		||||
 | 
			
		||||
class UpdateDbEncodingToUt8mb4 extends Migration
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Run the migrations.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function up()
 | 
			
		||||
    {
 | 
			
		||||
        // Migration removed due to issues during live migration.
 | 
			
		||||
        // Instead you can run the command `artisan bookstack:db-utf8mb4`
 | 
			
		||||
        // which will generate out the SQL request to upgrade your DB to utf8mb4.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reverse the migrations.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function down()
 | 
			
		||||
    {
 | 
			
		||||
        //
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,66 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Facades\Schema;
 | 
			
		||||
use Illuminate\Database\Schema\Blueprint;
 | 
			
		||||
use Illuminate\Database\Migrations\Migration;
 | 
			
		||||
 | 
			
		||||
class CreateCommentsTable extends Migration
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Run the migrations.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function up()
 | 
			
		||||
    {
 | 
			
		||||
        Schema::create('comments', function (Blueprint $table) {
 | 
			
		||||
            $table->increments('id')->unsigned();
 | 
			
		||||
            $table->integer('page_id')->unsigned();
 | 
			
		||||
            $table->longText('text')->nullable();
 | 
			
		||||
            $table->longText('html')->nullable();
 | 
			
		||||
            $table->integer('parent_id')->unsigned()->nullable();
 | 
			
		||||
            $table->integer('created_by')->unsigned();
 | 
			
		||||
            $table->integer('updated_by')->unsigned()->nullable();
 | 
			
		||||
            $table->boolean('active')->default(true);
 | 
			
		||||
 | 
			
		||||
            $table->index(['page_id']);
 | 
			
		||||
            $table->timestamps();
 | 
			
		||||
 | 
			
		||||
            // Assign new comment permissions to admin role
 | 
			
		||||
            $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id;
 | 
			
		||||
            // Create & attach new entity permissions
 | 
			
		||||
            $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
 | 
			
		||||
            $entity = 'Comment';
 | 
			
		||||
            foreach ($ops as $op) {
 | 
			
		||||
                $permissionId = DB::table('role_permissions')->insertGetId([
 | 
			
		||||
                    'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)),
 | 
			
		||||
                    'display_name' => $op . ' ' . $entity . 's',
 | 
			
		||||
                    'created_at' => \Carbon\Carbon::now()->toDateTimeString(),
 | 
			
		||||
                    'updated_at' => \Carbon\Carbon::now()->toDateTimeString()
 | 
			
		||||
                ]);
 | 
			
		||||
                DB::table('permission_role')->insert([
 | 
			
		||||
                    'role_id' => $adminRoleId,
 | 
			
		||||
                    'permission_id' => $permissionId
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reverse the migrations.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function down()
 | 
			
		||||
    {
 | 
			
		||||
        Schema::dropIfExists('comments');
 | 
			
		||||
        // Delete comment role permissions
 | 
			
		||||
        $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
 | 
			
		||||
        $entity = 'Comment';
 | 
			
		||||
        foreach ($ops as $op) {
 | 
			
		||||
            $permName = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op));
 | 
			
		||||
            DB::table('role_permissions')->where('name', '=', $permName)->delete();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								gulpfile.js
								
								
								
								
							
							
						
						
									
										28
									
								
								gulpfile.js
								
								
								
								
							| 
						 | 
				
			
			@ -14,8 +14,10 @@ const babelify = require("babelify");
 | 
			
		|||
const watchify = require("watchify");
 | 
			
		||||
const envify = require("envify");
 | 
			
		||||
const gutil = require("gulp-util");
 | 
			
		||||
const liveReload = require('gulp-livereload');
 | 
			
		||||
 | 
			
		||||
if (argv.production) process.env.NODE_ENV = 'production';
 | 
			
		||||
let isProduction = argv.production || process.env.NODE_ENV === 'production';
 | 
			
		||||
 | 
			
		||||
gulp.task('styles', () => {
 | 
			
		||||
    let chain = gulp.src(['resources/assets/sass/**/*.scss'])
 | 
			
		||||
| 
						 | 
				
			
			@ -26,31 +28,40 @@ gulp.task('styles', () => {
 | 
			
		|||
            }}))
 | 
			
		||||
        .pipe(sass())
 | 
			
		||||
        .pipe(autoprefixer('last 2 versions'));
 | 
			
		||||
    if (argv.production) chain = chain.pipe(minifycss());
 | 
			
		||||
    return chain.pipe(gulp.dest('public/css/'));
 | 
			
		||||
    if (isProduction) chain = chain.pipe(minifycss());
 | 
			
		||||
    return chain.pipe(gulp.dest('public/css/')).pipe(liveReload());
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function scriptTask(watch=false) {
 | 
			
		||||
function scriptTask(watch = false) {
 | 
			
		||||
 | 
			
		||||
    let props = {
 | 
			
		||||
        basedir: 'resources/assets/js',
 | 
			
		||||
        debug: true,
 | 
			
		||||
        entries: ['global.js']
 | 
			
		||||
        entries: ['global.js'],
 | 
			
		||||
        fast: !isProduction,
 | 
			
		||||
        cache: {},
 | 
			
		||||
        packageCache: {},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let bundler = watch ? watchify(browserify(props), { poll: true }) : browserify(props);
 | 
			
		||||
    bundler.transform(envify, {global: true}).transform(babelify, {presets: ['es2015']});
 | 
			
		||||
 | 
			
		||||
    if (isProduction) {
 | 
			
		||||
        bundler.transform(envify, {global: true}).transform(babelify, {presets: ['es2015']});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function rebundle() {
 | 
			
		||||
        let stream = bundler.bundle();
 | 
			
		||||
        stream = stream.pipe(source('common.js'));
 | 
			
		||||
        if (argv.production) stream = stream.pipe(buffer()).pipe(uglify());
 | 
			
		||||
        return stream.pipe(gulp.dest('public/js/'));
 | 
			
		||||
        if (isProduction) stream = stream.pipe(buffer()).pipe(uglify());
 | 
			
		||||
        return stream.pipe(gulp.dest('public/js/')).pipe(liveReload());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bundler.on('update', function() {
 | 
			
		||||
        rebundle();
 | 
			
		||||
        gutil.log('Rebundle...');
 | 
			
		||||
        gutil.log('Rebundling assets...');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    bundler.on('log', gutil.log);
 | 
			
		||||
    return rebundle();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +70,7 @@ gulp.task('scripts', () => {scriptTask(false)});
 | 
			
		|||
gulp.task('scripts-watch', () => {scriptTask(true)});
 | 
			
		||||
 | 
			
		||||
gulp.task('default', ['styles', 'scripts-watch'], () => {
 | 
			
		||||
    liveReload.listen();
 | 
			
		||||
    gulp.watch("resources/assets/sass/**/*.scss", ['styles']);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								package.json
								
								
								
								
							
							
						
						
									
										10
									
								
								package.json
								
								
								
								
							| 
						 | 
				
			
			@ -4,7 +4,8 @@
 | 
			
		|||
    "build": "gulp build",
 | 
			
		||||
    "production": "gulp build --production",
 | 
			
		||||
    "dev": "gulp",
 | 
			
		||||
    "watch": "gulp"
 | 
			
		||||
    "watch": "gulp",
 | 
			
		||||
    "permissions": "chown -R $USER:$USER bootstrap/cache storage public/uploads"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "babelify": "^7.3.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +14,7 @@
 | 
			
		|||
    "gulp": "3.9.1",
 | 
			
		||||
    "gulp-autoprefixer": "3.1.1",
 | 
			
		||||
    "gulp-clean-css": "^3.0.4",
 | 
			
		||||
    "gulp-livereload": "^3.8.1",
 | 
			
		||||
    "gulp-minify-css": "1.2.4",
 | 
			
		||||
    "gulp-plumber": "1.1.0",
 | 
			
		||||
    "gulp-sass": "3.1.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -29,15 +31,17 @@
 | 
			
		|||
    "angular-sanitize": "^1.5.5",
 | 
			
		||||
    "angular-ui-sortable": "^0.17.0",
 | 
			
		||||
    "axios": "^0.16.1",
 | 
			
		||||
    "babel-polyfill": "^6.23.0",
 | 
			
		||||
    "babel-preset-es2015": "^6.24.1",
 | 
			
		||||
    "clipboard": "^1.5.16",
 | 
			
		||||
    "clipboard": "^1.7.1",
 | 
			
		||||
    "codemirror": "^5.26.0",
 | 
			
		||||
    "dropzone": "^4.0.1",
 | 
			
		||||
    "gulp-util": "^3.0.8",
 | 
			
		||||
    "markdown-it": "^8.3.1",
 | 
			
		||||
    "markdown-it-task-lists": "^2.0.0",
 | 
			
		||||
    "moment": "^2.12.0",
 | 
			
		||||
    "vue": "^2.2.6"
 | 
			
		||||
    "vue": "^2.2.6",
 | 
			
		||||
    "vuedraggable": "^2.14.1"
 | 
			
		||||
  },
 | 
			
		||||
  "browser": {
 | 
			
		||||
    "vue": "vue/dist/vue.common.js"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										15
									
								
								readme.md
								
								
								
								
							
							
						
						
									
										15
									
								
								readme.md
								
								
								
								
							| 
						 | 
				
			
			@ -22,9 +22,12 @@ All development on BookStack is currently done on the master branch. When it's t
 | 
			
		|||
SASS is used to help the CSS development and the JavaScript is run through browserify/babel to allow for writing ES6 code. Both of these are done using gulp. To run the build task you can use the following commands:
 | 
			
		||||
 | 
			
		||||
``` bash
 | 
			
		||||
# Build and minify for production
 | 
			
		||||
# Build assets for development
 | 
			
		||||
npm run-script build
 | 
			
		||||
 | 
			
		||||
# Build and minify assets for production
 | 
			
		||||
npm run-script production
 | 
			
		||||
 | 
			
		||||
# Build for dev (With sourcemaps) and watch for changes
 | 
			
		||||
npm run-script dev
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			@ -64,17 +67,19 @@ The BookStack source is provided under the MIT License.
 | 
			
		|||
 | 
			
		||||
## Attribution
 | 
			
		||||
 | 
			
		||||
These are the great projects used to help build BookStack:
 | 
			
		||||
These are the great open-source projects used to help build BookStack:
 | 
			
		||||
 | 
			
		||||
* [Laravel](http://laravel.com/)
 | 
			
		||||
* [AngularJS](https://angularjs.org/)
 | 
			
		||||
* [jQuery](https://jquery.com/)
 | 
			
		||||
* [TinyMCE](https://www.tinymce.com/)
 | 
			
		||||
* [highlight.js](https://highlightjs.org/)
 | 
			
		||||
* [CodeMirror](https://codemirror.net)
 | 
			
		||||
* [Vue.js](http://vuejs.org/)
 | 
			
		||||
* [Axios](https://github.com/mzabriskie/axios)
 | 
			
		||||
* [jQuery Sortable](https://johnny.github.io/jquery-sortable/)
 | 
			
		||||
* [Material Design Iconic Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html)
 | 
			
		||||
* [Dropzone.js](http://www.dropzonejs.com/)
 | 
			
		||||
* [ZeroClipboard](http://zeroclipboard.org/)
 | 
			
		||||
* [clipboard.js](https://clipboardjs.com/)
 | 
			
		||||
* [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html)
 | 
			
		||||
* [markdown-it](https://github.com/markdown-it/markdown-it) and [markdown-it-task-lists](https://github.com/revin/markdown-it-task-lists)
 | 
			
		||||
* [Moment.js](http://momentjs.com/)
 | 
			
		||||
| 
						 | 
				
			
			@ -84,5 +89,3 @@ These are the great projects used to help build BookStack:
 | 
			
		|||
    * [Snappy (WKHTML2PDF)](https://github.com/barryvdh/laravel-snappy)
 | 
			
		||||
    * [Laravel IDE helper](https://github.com/barryvdh/laravel-ide-helper)
 | 
			
		||||
* [WKHTMLtoPDF](http://wkhtmltopdf.org/index.html)
 | 
			
		||||
 | 
			
		||||
Additionally, Thank you [BrowserStack](https://www.browserstack.com/) for supporting us and making cross-browser testing easy.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
require('codemirror/mode/css/css');
 | 
			
		||||
require('codemirror/mode/clike/clike');
 | 
			
		||||
require('codemirror/mode/diff/diff');
 | 
			
		||||
require('codemirror/mode/go/go');
 | 
			
		||||
require('codemirror/mode/htmlmixed/htmlmixed');
 | 
			
		||||
require('codemirror/mode/javascript/javascript');
 | 
			
		||||
| 
						 | 
				
			
			@ -17,40 +18,161 @@ require('codemirror/mode/yaml/yaml');
 | 
			
		|||
 | 
			
		||||
const CodeMirror = require('codemirror');
 | 
			
		||||
 | 
			
		||||
const modeMap = {
 | 
			
		||||
    css: 'css',
 | 
			
		||||
    c: 'clike',
 | 
			
		||||
    java: 'clike',
 | 
			
		||||
    scala: 'clike',
 | 
			
		||||
    kotlin: 'clike',
 | 
			
		||||
    'c++': 'clike',
 | 
			
		||||
    'c#': 'clike',
 | 
			
		||||
    csharp: 'clike',
 | 
			
		||||
    diff: 'diff',
 | 
			
		||||
    go: 'go',
 | 
			
		||||
    html: 'htmlmixed',
 | 
			
		||||
    javascript: 'javascript',
 | 
			
		||||
    json: {name: 'javascript', json: true},
 | 
			
		||||
    js: 'javascript',
 | 
			
		||||
    php: 'php',
 | 
			
		||||
    md: 'markdown',
 | 
			
		||||
    mdown: 'markdown',
 | 
			
		||||
    markdown: 'markdown',
 | 
			
		||||
    nginx: 'nginx',
 | 
			
		||||
    powershell: 'powershell',
 | 
			
		||||
    py: 'python',
 | 
			
		||||
    python: 'python',
 | 
			
		||||
    ruby: 'ruby',
 | 
			
		||||
    rb: 'ruby',
 | 
			
		||||
    shell: 'shell',
 | 
			
		||||
    sh: 'shell',
 | 
			
		||||
    bash: 'shell',
 | 
			
		||||
    toml: 'toml',
 | 
			
		||||
    sql: 'sql',
 | 
			
		||||
    xml: 'xml',
 | 
			
		||||
    yaml: 'yaml',
 | 
			
		||||
    yml: 'yaml',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.highlight = function() {
 | 
			
		||||
    let codeBlocks = document.querySelectorAll('.page-content pre');
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < codeBlocks.length; i++) {
 | 
			
		||||
        codeBlocks[i].innerHTML = codeBlocks[i].innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
 | 
			
		||||
        let content = codeBlocks[i].textContent;
 | 
			
		||||
        highlightElem(codeBlocks[i]);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
        CodeMirror(function(elt) {
 | 
			
		||||
            codeBlocks[i].parentNode.replaceChild(elt, codeBlocks[i]);
 | 
			
		||||
        }, {
 | 
			
		||||
            value: content,
 | 
			
		||||
            mode:  "",
 | 
			
		||||
            lineNumbers: true,
 | 
			
		||||
            theme: 'base16-light',
 | 
			
		||||
            readOnly: true
 | 
			
		||||
        });
 | 
			
		||||
function highlightElem(elem) {
 | 
			
		||||
    let innerCodeElem = elem.querySelector('code[class^=language-]');
 | 
			
		||||
    let mode = '';
 | 
			
		||||
    if (innerCodeElem !== null) {
 | 
			
		||||
        let langName = innerCodeElem.className.replace('language-', '');
 | 
			
		||||
        mode = getMode(langName);
 | 
			
		||||
    }
 | 
			
		||||
    elem.innerHTML = elem.innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
 | 
			
		||||
    let content = elem.textContent;
 | 
			
		||||
 | 
			
		||||
    CodeMirror(function(elt) {
 | 
			
		||||
        elem.parentNode.replaceChild(elt, elem);
 | 
			
		||||
    }, {
 | 
			
		||||
        value: content,
 | 
			
		||||
        mode:  mode,
 | 
			
		||||
        lineNumbers: true,
 | 
			
		||||
        theme: 'base16-light',
 | 
			
		||||
        readOnly: true
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Search for a codemirror code based off a user suggestion
 | 
			
		||||
 * @param suggestion
 | 
			
		||||
 * @returns {string}
 | 
			
		||||
 */
 | 
			
		||||
function getMode(suggestion) {
 | 
			
		||||
    suggestion = suggestion.trim().replace(/^\./g, '').toLowerCase();
 | 
			
		||||
    return (typeof modeMap[suggestion] !== 'undefined') ? modeMap[suggestion] : '';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports.highlightElem = highlightElem;
 | 
			
		||||
 | 
			
		||||
module.exports.wysiwygView = function(elem) {
 | 
			
		||||
    let doc = elem.ownerDocument;
 | 
			
		||||
    let codeElem = elem.querySelector('code');
 | 
			
		||||
 | 
			
		||||
    let lang = (elem.className || '').replace('language-', '');
 | 
			
		||||
    if (lang === '' && codeElem) {
 | 
			
		||||
        lang = (codeElem.className || '').replace('language-', '')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    elem.innerHTML = elem.innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
 | 
			
		||||
    let content = elem.textContent;
 | 
			
		||||
    let newWrap = doc.createElement('div');
 | 
			
		||||
    let newTextArea = doc.createElement('textarea');
 | 
			
		||||
 | 
			
		||||
    newWrap.className = 'CodeMirrorContainer';
 | 
			
		||||
    newWrap.setAttribute('data-lang', lang);
 | 
			
		||||
    newTextArea.style.display = 'none';
 | 
			
		||||
    elem.parentNode.replaceChild(newWrap, elem);
 | 
			
		||||
 | 
			
		||||
    newWrap.appendChild(newTextArea);
 | 
			
		||||
    newWrap.contentEditable = false;
 | 
			
		||||
    newTextArea.textContent = content;
 | 
			
		||||
 | 
			
		||||
    let cm = CodeMirror(function(elt) {
 | 
			
		||||
        newWrap.appendChild(elt);
 | 
			
		||||
    }, {
 | 
			
		||||
        value: content,
 | 
			
		||||
        mode:  getMode(lang),
 | 
			
		||||
        lineNumbers: true,
 | 
			
		||||
        theme: 'base16-light',
 | 
			
		||||
        readOnly: true
 | 
			
		||||
    });
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        cm.refresh();
 | 
			
		||||
    }, 300);
 | 
			
		||||
    return {wrap: newWrap, editor: cm};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.popupEditor = function(elem, modeSuggestion) {
 | 
			
		||||
    let content = elem.textContent;
 | 
			
		||||
 | 
			
		||||
    return CodeMirror(function(elt) {
 | 
			
		||||
        elem.parentNode.insertBefore(elt, elem);
 | 
			
		||||
        elem.style.display = 'none';
 | 
			
		||||
    }, {
 | 
			
		||||
        value: content,
 | 
			
		||||
        mode:  getMode(modeSuggestion),
 | 
			
		||||
        lineNumbers: true,
 | 
			
		||||
        theme: 'base16-light',
 | 
			
		||||
        lineWrapping: true
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.setMode = function(cmInstance, modeSuggestion) {
 | 
			
		||||
      cmInstance.setOption('mode', getMode(modeSuggestion));
 | 
			
		||||
};
 | 
			
		||||
module.exports.setContent = function(cmInstance, codeContent) {
 | 
			
		||||
    cmInstance.setValue(codeContent);
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        cmInstance.refresh();
 | 
			
		||||
    }, 10);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.markdownEditor = function(elem) {
 | 
			
		||||
    let content = elem.textContent;
 | 
			
		||||
 | 
			
		||||
    let cm = CodeMirror(function(elt) {
 | 
			
		||||
    return CodeMirror(function (elt) {
 | 
			
		||||
        elem.parentNode.insertBefore(elt, elem);
 | 
			
		||||
        elem.style.display = 'none';
 | 
			
		||||
    }, {
 | 
			
		||||
        value: content,
 | 
			
		||||
        mode:  "markdown",
 | 
			
		||||
        mode: "markdown",
 | 
			
		||||
        lineNumbers: true,
 | 
			
		||||
        theme: 'base16-light',
 | 
			
		||||
        lineWrapping: true
 | 
			
		||||
    });
 | 
			
		||||
    return cm;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.getMetaKey = function() {
 | 
			
		||||
    let mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
 | 
			
		||||
    return mac ? "Cmd" : "Ctrl";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
 | 
			
		||||
class BackToTop {
 | 
			
		||||
 | 
			
		||||
    constructor(elem) {
 | 
			
		||||
        this.elem = elem;
 | 
			
		||||
        this.targetElem = document.getElementById('header');
 | 
			
		||||
        this.showing = false;
 | 
			
		||||
        this.breakPoint = 1200;
 | 
			
		||||
        this.elem.addEventListener('click', this.scrollToTop.bind(this));
 | 
			
		||||
        window.addEventListener('scroll', this.onPageScroll.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onPageScroll() {
 | 
			
		||||
        let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
 | 
			
		||||
        if (!this.showing && scrollTopPos > this.breakPoint) {
 | 
			
		||||
            this.elem.style.display = 'block';
 | 
			
		||||
            this.showing = true;
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                this.elem.style.opacity = 0.4;
 | 
			
		||||
            }, 1);
 | 
			
		||||
        } else if (this.showing && scrollTopPos < this.breakPoint) {
 | 
			
		||||
            this.elem.style.opacity = 0;
 | 
			
		||||
            this.showing = false;
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                this.elem.style.display = 'none';
 | 
			
		||||
            }, 500);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    scrollToTop() {
 | 
			
		||||
        let targetTop = this.targetElem.getBoundingClientRect().top;
 | 
			
		||||
        let scrollElem = document.documentElement.scrollTop ? document.documentElement : document.body;
 | 
			
		||||
        let duration = 300;
 | 
			
		||||
        let start = Date.now();
 | 
			
		||||
        let scrollStart = this.targetElem.getBoundingClientRect().top;
 | 
			
		||||
 | 
			
		||||
        function setPos() {
 | 
			
		||||
            let percentComplete = (1-((Date.now() - start) / duration));
 | 
			
		||||
            let target = Math.abs(percentComplete * scrollStart);
 | 
			
		||||
            if (percentComplete > 0) {
 | 
			
		||||
                scrollElem.scrollTop = target;
 | 
			
		||||
                requestAnimationFrame(setPos.bind(this));
 | 
			
		||||
            } else {
 | 
			
		||||
                scrollElem.scrollTop = targetTop;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        requestAnimationFrame(setPos.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = BackToTop;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
 | 
			
		||||
class ChapterToggle {
 | 
			
		||||
 | 
			
		||||
    constructor(elem) {
 | 
			
		||||
        this.elem = elem;
 | 
			
		||||
        this.isOpen = elem.classList.contains('open');
 | 
			
		||||
        elem.addEventListener('click', this.click.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open() {
 | 
			
		||||
        let list = this.elem.parentNode.querySelector('.inset-list');
 | 
			
		||||
 | 
			
		||||
        this.elem.classList.add('open');
 | 
			
		||||
        list.style.display = 'block';
 | 
			
		||||
        list.style.height = '';
 | 
			
		||||
        let height = list.getBoundingClientRect().height;
 | 
			
		||||
        list.style.height = '0px';
 | 
			
		||||
        list.style.overflow = 'hidden';
 | 
			
		||||
        list.style.transition = 'height ease-in-out 240ms';
 | 
			
		||||
 | 
			
		||||
        let transitionEndBound = onTransitionEnd.bind(this);
 | 
			
		||||
        function onTransitionEnd() {
 | 
			
		||||
            list.style.overflow = '';
 | 
			
		||||
            list.style.height = '';
 | 
			
		||||
            list.style.transition = '';
 | 
			
		||||
            list.removeEventListener('transitionend', transitionEndBound);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            list.style.height = `${height}px`;
 | 
			
		||||
            list.addEventListener('transitionend', transitionEndBound)
 | 
			
		||||
        }, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    close() {
 | 
			
		||||
        let list = this.elem.parentNode.querySelector('.inset-list');
 | 
			
		||||
 | 
			
		||||
        this.elem.classList.remove('open');
 | 
			
		||||
        list.style.display =  'block';
 | 
			
		||||
        list.style.height = list.getBoundingClientRect().height + 'px';
 | 
			
		||||
        list.style.overflow = 'hidden';
 | 
			
		||||
        list.style.transition = 'height ease-in-out 240ms';
 | 
			
		||||
 | 
			
		||||
        let transitionEndBound = onTransitionEnd.bind(this);
 | 
			
		||||
        function onTransitionEnd() {
 | 
			
		||||
            list.style.overflow = '';
 | 
			
		||||
            list.style.height = '';
 | 
			
		||||
            list.style.transition = '';
 | 
			
		||||
            list.style.display =  'none';
 | 
			
		||||
            list.removeEventListener('transitionend', transitionEndBound);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            list.style.height = `0px`;
 | 
			
		||||
            list.addEventListener('transitionend', transitionEndBound)
 | 
			
		||||
        }, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    click(event) {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        this.isOpen ?  this.close() : this.open();
 | 
			
		||||
        this.isOpen = !this.isOpen;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = ChapterToggle;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Dropdown
 | 
			
		||||
 * Provides some simple logic to create simple dropdown menus.
 | 
			
		||||
 */
 | 
			
		||||
class DropDown {
 | 
			
		||||
 | 
			
		||||
    constructor(elem) {
 | 
			
		||||
        this.container = elem;
 | 
			
		||||
        this.menu = elem.querySelector('ul');
 | 
			
		||||
        this.toggle = elem.querySelector('[dropdown-toggle]');
 | 
			
		||||
        this.setupListeners();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show() {
 | 
			
		||||
        this.menu.style.display = 'block';
 | 
			
		||||
        this.menu.classList.add('anim', 'menuIn');
 | 
			
		||||
        this.container.addEventListener('mouseleave', this.hide.bind(this));
 | 
			
		||||
 | 
			
		||||
        // Focus on first input if existing
 | 
			
		||||
        let input = this.menu.querySelector('input');
 | 
			
		||||
        if (input !== null) input.focus();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hide() {
 | 
			
		||||
        this.menu.style.display = 'none';
 | 
			
		||||
        this.menu.classList.remove('anim', 'menuIn');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setupListeners() {
 | 
			
		||||
        // Hide menu on option click
 | 
			
		||||
        this.container.addEventListener('click', event => {
 | 
			
		||||
             let possibleChildren = Array.from(this.menu.querySelectorAll('a'));
 | 
			
		||||
             if (possibleChildren.indexOf(event.target) !== -1) this.hide();
 | 
			
		||||
        });
 | 
			
		||||
        // Show dropdown on toggle click
 | 
			
		||||
        this.toggle.addEventListener('click', this.show.bind(this));
 | 
			
		||||
        // Hide menu on enter press
 | 
			
		||||
        this.container.addEventListener('keypress', event => {
 | 
			
		||||
                if (event.keyCode !== 13) return true;
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
                this.hide();
 | 
			
		||||
                return false;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = DropDown;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
 | 
			
		||||
class ExpandToggle {
 | 
			
		||||
 | 
			
		||||
    constructor(elem) {
 | 
			
		||||
        this.elem = elem;
 | 
			
		||||
        this.isOpen = false;
 | 
			
		||||
        this.selector = elem.getAttribute('expand-toggle');
 | 
			
		||||
        elem.addEventListener('click', this.click.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open(elemToToggle) {
 | 
			
		||||
        elemToToggle.style.display = 'block';
 | 
			
		||||
        elemToToggle.style.height = '';
 | 
			
		||||
        let height = elemToToggle.getBoundingClientRect().height;
 | 
			
		||||
        elemToToggle.style.height = '0px';
 | 
			
		||||
        elemToToggle.style.overflow = 'hidden';
 | 
			
		||||
        elemToToggle.style.transition = 'height ease-in-out 240ms';
 | 
			
		||||
 | 
			
		||||
        let transitionEndBound = onTransitionEnd.bind(this);
 | 
			
		||||
        function onTransitionEnd() {
 | 
			
		||||
            elemToToggle.style.overflow = '';
 | 
			
		||||
            elemToToggle.style.height = '';
 | 
			
		||||
            elemToToggle.style.transition = '';
 | 
			
		||||
            elemToToggle.removeEventListener('transitionend', transitionEndBound);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            elemToToggle.style.height = `${height}px`;
 | 
			
		||||
            elemToToggle.addEventListener('transitionend', transitionEndBound)
 | 
			
		||||
        }, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    close(elemToToggle) {
 | 
			
		||||
        elemToToggle.style.display =  'block';
 | 
			
		||||
        elemToToggle.style.height = elemToToggle.getBoundingClientRect().height + 'px';
 | 
			
		||||
        elemToToggle.style.overflow = 'hidden';
 | 
			
		||||
        elemToToggle.style.transition = 'all ease-in-out 240ms';
 | 
			
		||||
 | 
			
		||||
        let transitionEndBound = onTransitionEnd.bind(this);
 | 
			
		||||
        function onTransitionEnd() {
 | 
			
		||||
            elemToToggle.style.overflow = '';
 | 
			
		||||
            elemToToggle.style.height = '';
 | 
			
		||||
            elemToToggle.style.transition = '';
 | 
			
		||||
            elemToToggle.style.display =  'none';
 | 
			
		||||
            elemToToggle.removeEventListener('transitionend', transitionEndBound);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            elemToToggle.style.height = `0px`;
 | 
			
		||||
            elemToToggle.addEventListener('transitionend', transitionEndBound)
 | 
			
		||||
        }, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    click(event) {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        let matchingElems = document.querySelectorAll(this.selector);
 | 
			
		||||
        for (let i = 0, len = matchingElems.length; i < len; i++) {
 | 
			
		||||
            this.isOpen ?  this.close(matchingElems[i]) : this.open(matchingElems[i]);
 | 
			
		||||
        }
 | 
			
		||||
        this.isOpen = !this.isOpen;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = ExpandToggle;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
 | 
			
		||||
let componentMapping = {
 | 
			
		||||
    'dropdown': require('./dropdown'),
 | 
			
		||||
    'overlay': require('./overlay'),
 | 
			
		||||
    'back-to-top': require('./back-top-top'),
 | 
			
		||||
    'notification': require('./notification'),
 | 
			
		||||
    'chapter-toggle': require('./chapter-toggle'),
 | 
			
		||||
    'expand-toggle': require('./expand-toggle'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
window.components = {};
 | 
			
		||||
 | 
			
		||||
let componentNames = Object.keys(componentMapping);
 | 
			
		||||
 | 
			
		||||
for (let i = 0, len = componentNames.length; i < len; i++) {
 | 
			
		||||
    let name = componentNames[i];
 | 
			
		||||
    let elems = document.querySelectorAll(`[${name}]`);
 | 
			
		||||
    if (elems.length === 0) continue;
 | 
			
		||||
 | 
			
		||||
    let component = componentMapping[name];
 | 
			
		||||
    if (typeof window.components[name] === "undefined") window.components[name] = [];
 | 
			
		||||
    for (let j = 0, jLen = elems.length; j < jLen; j++) {
 | 
			
		||||
         let instance = new component(elems[j]);
 | 
			
		||||
         if (typeof elems[j].components === 'undefined') elems[j].components = {};
 | 
			
		||||
         elems[j].components[name] = instance;
 | 
			
		||||
         window.components[name].push(instance);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
 | 
			
		||||
class Notification {
 | 
			
		||||
 | 
			
		||||
    constructor(elem) {
 | 
			
		||||
        this.elem = elem;
 | 
			
		||||
        this.type = elem.getAttribute('notification');
 | 
			
		||||
        this.textElem = elem.querySelector('span');
 | 
			
		||||
        this.autohide = this.elem.hasAttribute('data-autohide');
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        this.hideCleanup = this.hideCleanup.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show(textToShow = '') {
 | 
			
		||||
        this.elem.removeEventListener('transitionend', this.hideCleanup);
 | 
			
		||||
        this.textElem.textContent = textToShow;
 | 
			
		||||
        this.elem.style.display = 'block';
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            this.elem.classList.add('showing');
 | 
			
		||||
        }, 1);
 | 
			
		||||
 | 
			
		||||
        if (this.autohide) setTimeout(this.hide.bind(this), 2000);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hide() {
 | 
			
		||||
        this.elem.classList.remove('showing');
 | 
			
		||||
        this.elem.addEventListener('transitionend', this.hideCleanup);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hideCleanup() {
 | 
			
		||||
        this.elem.style.display = 'none';
 | 
			
		||||
        this.elem.removeEventListener('transitionend', this.hideCleanup);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = Notification;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
 | 
			
		||||
class Overlay {
 | 
			
		||||
 | 
			
		||||
    constructor(elem) {
 | 
			
		||||
        this.container = elem;
 | 
			
		||||
        elem.addEventListener('click', event => {
 | 
			
		||||
             if (event.target === elem) return this.hide();
 | 
			
		||||
        });
 | 
			
		||||
        let closeButtons = elem.querySelectorAll('.overlay-close');
 | 
			
		||||
        for (let i=0; i < closeButtons.length; i++) {
 | 
			
		||||
            closeButtons[i].addEventListener('click', this.hide.bind(this));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toggle(show = true) {
 | 
			
		||||
        let start = Date.now();
 | 
			
		||||
        let duration = 240;
 | 
			
		||||
 | 
			
		||||
        function setOpacity() {
 | 
			
		||||
            let elapsedTime = (Date.now() - start);
 | 
			
		||||
            let targetOpacity = show ? (elapsedTime / duration) : 1-(elapsedTime / duration);
 | 
			
		||||
            this.container.style.opacity = targetOpacity;
 | 
			
		||||
            if (elapsedTime > duration) {
 | 
			
		||||
                this.container.style.display = show ? 'flex' : 'none';
 | 
			
		||||
                this.container.style.opacity = '';
 | 
			
		||||
            } else {
 | 
			
		||||
                requestAnimationFrame(setOpacity.bind(this));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        requestAnimationFrame(setOpacity.bind(this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hide() { this.toggle(false); }
 | 
			
		||||
    show() { this.toggle(true); }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = Overlay;
 | 
			
		||||
| 
						 | 
				
			
			@ -8,256 +8,6 @@ moment.locale('en-gb');
 | 
			
		|||
 | 
			
		||||
module.exports = function (ngApp, events) {
 | 
			
		||||
 | 
			
		||||
    ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService',
 | 
			
		||||
        function ($scope, $attrs, $http, $timeout, imageManagerService) {
 | 
			
		||||
 | 
			
		||||
            $scope.images = [];
 | 
			
		||||
            $scope.imageType = $attrs.imageType;
 | 
			
		||||
            $scope.selectedImage = false;
 | 
			
		||||
            $scope.dependantPages = false;
 | 
			
		||||
            $scope.showing = false;
 | 
			
		||||
            $scope.hasMore = false;
 | 
			
		||||
            $scope.imageUpdateSuccess = false;
 | 
			
		||||
            $scope.imageDeleteSuccess = false;
 | 
			
		||||
            $scope.uploadedTo = $attrs.uploadedTo;
 | 
			
		||||
            $scope.view = 'all';
 | 
			
		||||
 | 
			
		||||
            $scope.searching = false;
 | 
			
		||||
            $scope.searchTerm = '';
 | 
			
		||||
 | 
			
		||||
            let page = 0;
 | 
			
		||||
            let previousClickTime = 0;
 | 
			
		||||
            let previousClickImage = 0;
 | 
			
		||||
            let dataLoaded = false;
 | 
			
		||||
            let callback = false;
 | 
			
		||||
 | 
			
		||||
            let preSearchImages = [];
 | 
			
		||||
            let preSearchHasMore = false;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Used by dropzone to get the endpoint to upload to.
 | 
			
		||||
             * @returns {string}
 | 
			
		||||
             */
 | 
			
		||||
            $scope.getUploadUrl = function () {
 | 
			
		||||
                return window.baseUrl('/images/' + $scope.imageType + '/upload');
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Cancel the current search operation.
 | 
			
		||||
             */
 | 
			
		||||
            function cancelSearch() {
 | 
			
		||||
                $scope.searching = false;
 | 
			
		||||
                $scope.searchTerm = '';
 | 
			
		||||
                $scope.images = preSearchImages;
 | 
			
		||||
                $scope.hasMore = preSearchHasMore;
 | 
			
		||||
            }
 | 
			
		||||
            $scope.cancelSearch = cancelSearch;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Runs on image upload, Adds an image to local list of images
 | 
			
		||||
             * and shows a success message to the user.
 | 
			
		||||
             * @param file
 | 
			
		||||
             * @param data
 | 
			
		||||
             */
 | 
			
		||||
            $scope.uploadSuccess = function (file, data) {
 | 
			
		||||
                $scope.$apply(() => {
 | 
			
		||||
                    $scope.images.unshift(data);
 | 
			
		||||
                });
 | 
			
		||||
                events.emit('success', trans('components.image_upload_success'));
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Runs the callback and hides the image manager.
 | 
			
		||||
             * @param returnData
 | 
			
		||||
             */
 | 
			
		||||
            function callbackAndHide(returnData) {
 | 
			
		||||
                if (callback) callback(returnData);
 | 
			
		||||
                $scope.hide();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Image select action. Checks if a double-click was fired.
 | 
			
		||||
             * @param image
 | 
			
		||||
             */
 | 
			
		||||
            $scope.imageSelect = function (image) {
 | 
			
		||||
                let dblClickTime = 300;
 | 
			
		||||
                let currentTime = Date.now();
 | 
			
		||||
                let timeDiff = currentTime - previousClickTime;
 | 
			
		||||
 | 
			
		||||
                if (timeDiff < dblClickTime && image.id === previousClickImage) {
 | 
			
		||||
                    // If double click
 | 
			
		||||
                    callbackAndHide(image);
 | 
			
		||||
                } else {
 | 
			
		||||
                    // If single
 | 
			
		||||
                    $scope.selectedImage = image;
 | 
			
		||||
                    $scope.dependantPages = false;
 | 
			
		||||
                }
 | 
			
		||||
                previousClickTime = currentTime;
 | 
			
		||||
                previousClickImage = image.id;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Action that runs when the 'Select image' button is clicked.
 | 
			
		||||
             * Runs the callback and hides the image manager.
 | 
			
		||||
             */
 | 
			
		||||
            $scope.selectButtonClick = function () {
 | 
			
		||||
                callbackAndHide($scope.selectedImage);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Show the image manager.
 | 
			
		||||
             * Takes a callback to execute later on.
 | 
			
		||||
             * @param doneCallback
 | 
			
		||||
             */
 | 
			
		||||
            function show(doneCallback) {
 | 
			
		||||
                callback = doneCallback;
 | 
			
		||||
                $scope.showing = true;
 | 
			
		||||
                $('#image-manager').find('.overlay').css('display', 'flex').hide().fadeIn(240);
 | 
			
		||||
                // Get initial images if they have not yet been loaded in.
 | 
			
		||||
                if (!dataLoaded) {
 | 
			
		||||
                    fetchData();
 | 
			
		||||
                    dataLoaded = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Connects up the image manger so it can be used externally
 | 
			
		||||
            // such as from TinyMCE.
 | 
			
		||||
            imageManagerService.show = show;
 | 
			
		||||
            imageManagerService.showExternal = function (doneCallback) {
 | 
			
		||||
                $scope.$apply(() => {
 | 
			
		||||
                    show(doneCallback);
 | 
			
		||||
                });
 | 
			
		||||
            };
 | 
			
		||||
            window.ImageManager = imageManagerService;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Hide the image manager
 | 
			
		||||
             */
 | 
			
		||||
            $scope.hide = function () {
 | 
			
		||||
                $scope.showing = false;
 | 
			
		||||
                $('#image-manager').find('.overlay').fadeOut(240);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let baseUrl = window.baseUrl('/images/' + $scope.imageType + '/all/');
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Fetch the list image data from the server.
 | 
			
		||||
             */
 | 
			
		||||
            function fetchData() {
 | 
			
		||||
                let url = baseUrl + page + '?';
 | 
			
		||||
                let components = {};
 | 
			
		||||
                if ($scope.uploadedTo) components['page_id'] = $scope.uploadedTo;
 | 
			
		||||
                if ($scope.searching) components['term'] = $scope.searchTerm;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                url += Object.keys(components).map((key) => {
 | 
			
		||||
                    return key + '=' + encodeURIComponent(components[key]);
 | 
			
		||||
                }).join('&');
 | 
			
		||||
 | 
			
		||||
                $http.get(url).then((response) => {
 | 
			
		||||
                    $scope.images = $scope.images.concat(response.data.images);
 | 
			
		||||
                    $scope.hasMore = response.data.hasMore;
 | 
			
		||||
                    page++;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            $scope.fetchData = fetchData;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Start a search operation
 | 
			
		||||
             */
 | 
			
		||||
            $scope.searchImages = function() {
 | 
			
		||||
 | 
			
		||||
                if ($scope.searchTerm === '') {
 | 
			
		||||
                    cancelSearch();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!$scope.searching) {
 | 
			
		||||
                    preSearchImages = $scope.images;
 | 
			
		||||
                    preSearchHasMore = $scope.hasMore;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $scope.searching = true;
 | 
			
		||||
                $scope.images = [];
 | 
			
		||||
                $scope.hasMore = false;
 | 
			
		||||
                page = 0;
 | 
			
		||||
                baseUrl = window.baseUrl('/images/' + $scope.imageType + '/search/');
 | 
			
		||||
                fetchData();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Set the current image listing view.
 | 
			
		||||
             * @param viewName
 | 
			
		||||
             */
 | 
			
		||||
            $scope.setView = function(viewName) {
 | 
			
		||||
                cancelSearch();
 | 
			
		||||
                $scope.images = [];
 | 
			
		||||
                $scope.hasMore = false;
 | 
			
		||||
                page = 0;
 | 
			
		||||
                $scope.view = viewName;
 | 
			
		||||
                baseUrl = window.baseUrl('/images/' + $scope.imageType  + '/' + viewName + '/');
 | 
			
		||||
                fetchData();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Save the details of an image.
 | 
			
		||||
             * @param event
 | 
			
		||||
             */
 | 
			
		||||
            $scope.saveImageDetails = function (event) {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
                let url = window.baseUrl('/images/update/' + $scope.selectedImage.id);
 | 
			
		||||
                $http.put(url, this.selectedImage).then(response => {
 | 
			
		||||
                    events.emit('success', trans('components.image_update_success'));
 | 
			
		||||
                }, (response) => {
 | 
			
		||||
                    if (response.status === 422) {
 | 
			
		||||
                        let errors = response.data;
 | 
			
		||||
                        let message = '';
 | 
			
		||||
                        Object.keys(errors).forEach((key) => {
 | 
			
		||||
                            message += errors[key].join('\n');
 | 
			
		||||
                        });
 | 
			
		||||
                        events.emit('error', message);
 | 
			
		||||
                    } else if (response.status === 403) {
 | 
			
		||||
                        events.emit('error', response.data.error);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Delete an image from system and notify of success.
 | 
			
		||||
             * Checks if it should force delete when an image
 | 
			
		||||
             * has dependant pages.
 | 
			
		||||
             * @param event
 | 
			
		||||
             */
 | 
			
		||||
            $scope.deleteImage = function (event) {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
                let force = $scope.dependantPages !== false;
 | 
			
		||||
                let url = window.baseUrl('/images/' + $scope.selectedImage.id);
 | 
			
		||||
                if (force) url += '?force=true';
 | 
			
		||||
                $http.delete(url).then((response) => {
 | 
			
		||||
                    $scope.images.splice($scope.images.indexOf($scope.selectedImage), 1);
 | 
			
		||||
                    $scope.selectedImage = false;
 | 
			
		||||
                    events.emit('success', trans('components.image_delete_success'));
 | 
			
		||||
                }, (response) => {
 | 
			
		||||
                    // Pages failure
 | 
			
		||||
                    if (response.status === 400) {
 | 
			
		||||
                        $scope.dependantPages = response.data;
 | 
			
		||||
                    } else if (response.status === 403) {
 | 
			
		||||
                        events.emit('error', response.data.error);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Simple date creator used to properly format dates.
 | 
			
		||||
             * @param stringDate
 | 
			
		||||
             * @returns {Date}
 | 
			
		||||
             */
 | 
			
		||||
            $scope.getDate = function (stringDate) {
 | 
			
		||||
                return new Date(stringDate);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        }]);
 | 
			
		||||
 | 
			
		||||
    ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', '$sce',
 | 
			
		||||
        function ($scope, $http, $attrs, $interval, $timeout, $sce) {
 | 
			
		||||
| 
						 | 
				
			
			@ -370,14 +120,8 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
            saveDraft();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Listen to shortcuts coming via events
 | 
			
		||||
        $scope.$on('editor-keydown', (event, data) => {
 | 
			
		||||
            // Save shortcut (ctrl+s)
 | 
			
		||||
            if (data.keyCode == 83 && (navigator.platform.match("Mac") ? data.metaKey : data.ctrlKey)) {
 | 
			
		||||
                data.preventDefault();
 | 
			
		||||
                saveDraft();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        // Listen to save draft events from editor
 | 
			
		||||
        $scope.$on('save-draft', saveDraft);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Discard the current draft and grab the current page
 | 
			
		||||
| 
						 | 
				
			
			@ -385,7 +129,7 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
         */
 | 
			
		||||
        $scope.discardDraft = function () {
 | 
			
		||||
            let url = window.baseUrl('/ajax/page/' + pageId);
 | 
			
		||||
            $http.get(url).then((responseData) => {
 | 
			
		||||
            $http.get(url).then(responseData => {
 | 
			
		||||
                if (autoSave) $interval.cancel(autoSave);
 | 
			
		||||
                $scope.draftText = trans('entities.pages_editing_page');
 | 
			
		||||
                $scope.isUpdateDraft = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -401,90 +145,6 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
 | 
			
		||||
    }]);
 | 
			
		||||
 | 
			
		||||
    ngApp.controller('PageTagController', ['$scope', '$http', '$attrs',
 | 
			
		||||
        function ($scope, $http, $attrs) {
 | 
			
		||||
 | 
			
		||||
            const pageId = Number($attrs.pageId);
 | 
			
		||||
            $scope.tags = [];
 | 
			
		||||
 | 
			
		||||
            $scope.sortOptions = {
 | 
			
		||||
                handle: '.handle',
 | 
			
		||||
                items: '> tr',
 | 
			
		||||
                containment: "parent",
 | 
			
		||||
                axis: "y"
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Push an empty tag to the end of the scope tags.
 | 
			
		||||
             */
 | 
			
		||||
            function addEmptyTag() {
 | 
			
		||||
                $scope.tags.push({
 | 
			
		||||
                    name: '',
 | 
			
		||||
                    value: ''
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            $scope.addEmptyTag = addEmptyTag;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Get all tags for the current book and add into scope.
 | 
			
		||||
             */
 | 
			
		||||
            function getTags() {
 | 
			
		||||
                let url = window.baseUrl(`/ajax/tags/get/page/${pageId}`);
 | 
			
		||||
                $http.get(url).then((responseData) => {
 | 
			
		||||
                    $scope.tags = responseData.data;
 | 
			
		||||
                    addEmptyTag();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            getTags();
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Set the order property on all tags.
 | 
			
		||||
             */
 | 
			
		||||
            function setTagOrder() {
 | 
			
		||||
                for (let i = 0; i < $scope.tags.length; i++) {
 | 
			
		||||
                    $scope.tags[i].order = i;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * When an tag changes check if another empty editable
 | 
			
		||||
             * field needs to be added onto the end.
 | 
			
		||||
             * @param tag
 | 
			
		||||
             */
 | 
			
		||||
            $scope.tagChange = function(tag) {
 | 
			
		||||
                let cPos = $scope.tags.indexOf(tag);
 | 
			
		||||
                if (cPos !== $scope.tags.length-1) return;
 | 
			
		||||
 | 
			
		||||
                if (tag.name !== '' || tag.value !== '') {
 | 
			
		||||
                    addEmptyTag();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * When an tag field loses focus check the tag to see if its
 | 
			
		||||
             * empty and therefore could be removed from the list.
 | 
			
		||||
             * @param tag
 | 
			
		||||
             */
 | 
			
		||||
            $scope.tagBlur = function(tag) {
 | 
			
		||||
                let isLast = $scope.tags.length - 1 === $scope.tags.indexOf(tag);
 | 
			
		||||
                if (tag.name === '' && tag.value === '' && !isLast) {
 | 
			
		||||
                    let cPos = $scope.tags.indexOf(tag);
 | 
			
		||||
                    $scope.tags.splice(cPos, 1);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Remove a tag from the current list.
 | 
			
		||||
             * @param tag
 | 
			
		||||
             */
 | 
			
		||||
            $scope.removeTag = function(tag) {
 | 
			
		||||
                let cIndex = $scope.tags.indexOf(tag);
 | 
			
		||||
                $scope.tags.splice(cIndex, 1);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        }]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    ngApp.controller('PageAttachmentController', ['$scope', '$http', '$attrs',
 | 
			
		||||
        function ($scope, $http, $attrs) {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -114,39 +114,6 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
        };
 | 
			
		||||
    }]);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Dropdown
 | 
			
		||||
     * Provides some simple logic to create small dropdown menus
 | 
			
		||||
     */
 | 
			
		||||
    ngApp.directive('dropdown', [function () {
 | 
			
		||||
        return {
 | 
			
		||||
            restrict: 'A',
 | 
			
		||||
            link: function (scope, element, attrs) {
 | 
			
		||||
                const menu = element.find('ul');
 | 
			
		||||
                element.find('[dropdown-toggle]').on('click', function () {
 | 
			
		||||
                    menu.show().addClass('anim menuIn');
 | 
			
		||||
                    let inputs = menu.find('input');
 | 
			
		||||
                    let hasInput = inputs.length > 0;
 | 
			
		||||
                    if (hasInput) {
 | 
			
		||||
                        inputs.first().focus();
 | 
			
		||||
                        element.on('keypress', 'input', event => {
 | 
			
		||||
                            if (event.keyCode === 13) {
 | 
			
		||||
                                event.preventDefault();
 | 
			
		||||
                                menu.hide();
 | 
			
		||||
                                menu.removeClass('anim menuIn');
 | 
			
		||||
                                return false;
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    element.mouseleave(function () {
 | 
			
		||||
                        menu.hide();
 | 
			
		||||
                        menu.removeClass('anim menuIn');
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }]);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * TinyMCE
 | 
			
		||||
     * An angular wrapper around the tinyMCE editor.
 | 
			
		||||
| 
						 | 
				
			
			@ -187,30 +154,6 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
                }
 | 
			
		||||
 | 
			
		||||
                scope.tinymce.extraSetups.push(tinyMceSetup);
 | 
			
		||||
 | 
			
		||||
                // Custom tinyMCE plugins
 | 
			
		||||
                tinymce.PluginManager.add('customhr', function (editor) {
 | 
			
		||||
                    editor.addCommand('InsertHorizontalRule', function () {
 | 
			
		||||
                        let hrElem = document.createElement('hr');
 | 
			
		||||
                        let cNode = editor.selection.getNode();
 | 
			
		||||
                        let parentNode = cNode.parentNode;
 | 
			
		||||
                        parentNode.insertBefore(hrElem, cNode);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    editor.addButton('hr', {
 | 
			
		||||
                        icon: 'hr',
 | 
			
		||||
                        tooltip: 'Horizontal line',
 | 
			
		||||
                        cmd: 'InsertHorizontalRule'
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    editor.addMenuItem('hr', {
 | 
			
		||||
                        icon: 'hr',
 | 
			
		||||
                        text: 'Horizontal line',
 | 
			
		||||
                        cmd: 'InsertHorizontalRule',
 | 
			
		||||
                        context: 'insert'
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                tinymce.init(scope.tinymce);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -232,15 +175,48 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
            },
 | 
			
		||||
            link: function (scope, element, attrs) {
 | 
			
		||||
 | 
			
		||||
                // Set initial model content
 | 
			
		||||
                element = element.find('textarea').first();
 | 
			
		||||
 | 
			
		||||
                // Codemirror Setup
 | 
			
		||||
                element = element.find('textarea').first();
 | 
			
		||||
                let cm = code.markdownEditor(element[0]);
 | 
			
		||||
 | 
			
		||||
                // Custom key commands
 | 
			
		||||
                let metaKey = code.getMetaKey();
 | 
			
		||||
                const extraKeys = {};
 | 
			
		||||
                // Insert Image shortcut
 | 
			
		||||
                extraKeys[`${metaKey}-Alt-I`] = function(cm) {
 | 
			
		||||
                    let selectedText = cm.getSelection();
 | 
			
		||||
                    let newText = ``;
 | 
			
		||||
                    let cursorPos = cm.getCursor('from');
 | 
			
		||||
                    cm.replaceSelection(newText);
 | 
			
		||||
                    cm.setCursor(cursorPos.line, cursorPos.ch + newText.length -1);
 | 
			
		||||
                };
 | 
			
		||||
                // Save draft
 | 
			
		||||
                extraKeys[`${metaKey}-S`] = function(cm) {scope.$emit('save-draft');};
 | 
			
		||||
                // Show link selector
 | 
			
		||||
                extraKeys[`Shift-${metaKey}-K`] = function(cm) {showLinkSelector()};
 | 
			
		||||
                // Insert Link
 | 
			
		||||
                extraKeys[`${metaKey}-K`] = function(cm) {insertLink()};
 | 
			
		||||
                // FormatShortcuts
 | 
			
		||||
                extraKeys[`${metaKey}-1`] = function(cm) {replaceLineStart('##');};
 | 
			
		||||
                extraKeys[`${metaKey}-2`] = function(cm) {replaceLineStart('###');};
 | 
			
		||||
                extraKeys[`${metaKey}-3`] = function(cm) {replaceLineStart('####');};
 | 
			
		||||
                extraKeys[`${metaKey}-4`] = function(cm) {replaceLineStart('#####');};
 | 
			
		||||
                extraKeys[`${metaKey}-5`] = function(cm) {replaceLineStart('');};
 | 
			
		||||
                extraKeys[`${metaKey}-d`] = function(cm) {replaceLineStart('');};
 | 
			
		||||
                extraKeys[`${metaKey}-6`] = function(cm) {replaceLineStart('>');};
 | 
			
		||||
                extraKeys[`${metaKey}-q`] = function(cm) {replaceLineStart('>');};
 | 
			
		||||
                extraKeys[`${metaKey}-7`] = function(cm) {wrapSelection('\n```\n', '\n```');};
 | 
			
		||||
                extraKeys[`${metaKey}-8`] = function(cm) {wrapSelection('`', '`');};
 | 
			
		||||
                extraKeys[`Shift-${metaKey}-E`] = function(cm) {wrapSelection('`', '`');};
 | 
			
		||||
                extraKeys[`${metaKey}-9`] = function(cm) {wrapSelection('<p class="callout info">', '</div>');};
 | 
			
		||||
                cm.setOption('extraKeys', extraKeys);
 | 
			
		||||
 | 
			
		||||
                // Update data on content change
 | 
			
		||||
                cm.on('change', (instance, changeObj) => {
 | 
			
		||||
                    update(instance);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Handle scroll to sync display view
 | 
			
		||||
                cm.on('scroll', instance => {
 | 
			
		||||
                    // Thanks to http://liuhao.im/english/2015/11/10/the-sync-scroll-of-markdown-editor-in-javascript.html
 | 
			
		||||
                    let scroll = instance.getScrollInfo();
 | 
			
		||||
| 
						 | 
				
			
			@ -257,6 +233,166 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
                    scope.$emit('markdown-scroll', totalLines.length);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Handle image paste
 | 
			
		||||
                cm.on('paste', (cm, event) => {
 | 
			
		||||
                    if (!event.clipboardData || !event.clipboardData.items) return;
 | 
			
		||||
                    for (let i = 0; i < event.clipboardData.items.length; i++) {
 | 
			
		||||
                        uploadImage(event.clipboardData.items[i].getAsFile());
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Handle images on drag-drop
 | 
			
		||||
                cm.on('drop', (cm, event) => {
 | 
			
		||||
                    event.stopPropagation();
 | 
			
		||||
                    event.preventDefault();
 | 
			
		||||
                    let cursorPos = cm.coordsChar({left: event.pageX, top: event.pageY});
 | 
			
		||||
                    cm.setCursor(cursorPos);
 | 
			
		||||
                    if (!event.dataTransfer || !event.dataTransfer.files) return;
 | 
			
		||||
                    for (let i = 0; i < event.dataTransfer.files.length; i++) {
 | 
			
		||||
                        uploadImage(event.dataTransfer.files[i]);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Helper to replace editor content
 | 
			
		||||
                function replaceContent(search, replace) {
 | 
			
		||||
                    let text = cm.getValue();
 | 
			
		||||
                    let cursor = cm.listSelections();
 | 
			
		||||
                    cm.setValue(text.replace(search, replace));
 | 
			
		||||
                    cm.setSelections(cursor);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Helper to replace the start of the line
 | 
			
		||||
                function replaceLineStart(newStart) {
 | 
			
		||||
                    let cursor = cm.getCursor();
 | 
			
		||||
                    let lineContent = cm.getLine(cursor.line);
 | 
			
		||||
                    let lineLen = lineContent.length;
 | 
			
		||||
                    let lineStart = lineContent.split(' ')[0];
 | 
			
		||||
 | 
			
		||||
                    // Remove symbol if already set
 | 
			
		||||
                    if (lineStart === newStart) {
 | 
			
		||||
                        lineContent = lineContent.replace(`${newStart} `, '');
 | 
			
		||||
                        cm.replaceRange(lineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen});
 | 
			
		||||
                        cm.setCursor({line: cursor.line, ch: cursor.ch - (newStart.length + 1)});
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    let alreadySymbol = /^[#>`]/.test(lineStart);
 | 
			
		||||
                    let posDif = 0;
 | 
			
		||||
                    if (alreadySymbol) {
 | 
			
		||||
                        posDif = newStart.length - lineStart.length;
 | 
			
		||||
                        lineContent = lineContent.replace(lineStart, newStart).trim();
 | 
			
		||||
                    } else if (newStart !== '') {
 | 
			
		||||
                        posDif = newStart.length + 1;
 | 
			
		||||
                        lineContent = newStart + ' ' + lineContent;
 | 
			
		||||
                    }
 | 
			
		||||
                    cm.replaceRange(lineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen});
 | 
			
		||||
                    cm.setCursor({line: cursor.line, ch: cursor.ch + posDif});
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function wrapLine(start, end) {
 | 
			
		||||
                    let cursor = cm.getCursor();
 | 
			
		||||
                    let lineContent = cm.getLine(cursor.line);
 | 
			
		||||
                    let lineLen = lineContent.length;
 | 
			
		||||
                    let newLineContent = lineContent;
 | 
			
		||||
 | 
			
		||||
                    if (lineContent.indexOf(start) === 0 && lineContent.slice(-end.length) === end) {
 | 
			
		||||
                        newLineContent = lineContent.slice(start.length, lineContent.length - end.length);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        newLineContent = `${start}${lineContent}${end}`;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    cm.replaceRange(newLineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen});
 | 
			
		||||
                    cm.setCursor({line: cursor.line, ch: cursor.ch + (newLineContent.length - lineLen)});
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function wrapSelection(start, end) {
 | 
			
		||||
                    let selection = cm.getSelection();
 | 
			
		||||
                    if (selection === '') return wrapLine(start, end);
 | 
			
		||||
                    let newSelection = selection;
 | 
			
		||||
                    let frontDiff = 0;
 | 
			
		||||
                    let endDiff = 0;
 | 
			
		||||
 | 
			
		||||
                    if (selection.indexOf(start) === 0 && selection.slice(-end.length) === end) {
 | 
			
		||||
                        newSelection = selection.slice(start.length, selection.length - end.length);
 | 
			
		||||
                        endDiff = -(end.length + start.length);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        newSelection = `${start}${selection}${end}`;
 | 
			
		||||
                        endDiff = start.length + end.length;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    let selections = cm.listSelections()[0];
 | 
			
		||||
                    cm.replaceSelection(newSelection);
 | 
			
		||||
                    let headFirst = selections.head.ch <= selections.anchor.ch;
 | 
			
		||||
                    selections.head.ch += headFirst ? frontDiff : endDiff;
 | 
			
		||||
                    selections.anchor.ch += headFirst ? endDiff : frontDiff;
 | 
			
		||||
                    cm.setSelections([selections]);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Handle image upload and add image into markdown content
 | 
			
		||||
                function uploadImage(file) {
 | 
			
		||||
                    if (file === null || file.type.indexOf('image') !== 0) return;
 | 
			
		||||
                    let ext = 'png';
 | 
			
		||||
 | 
			
		||||
                    if (file.name) {
 | 
			
		||||
                        let fileNameMatches = file.name.match(/\.(.+)$/);
 | 
			
		||||
                        if (fileNameMatches.length > 1) ext = fileNameMatches[1];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Insert image into markdown
 | 
			
		||||
                    let id = "image-" + Math.random().toString(16).slice(2);
 | 
			
		||||
                    let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
 | 
			
		||||
                    let selectedText = cm.getSelection();
 | 
			
		||||
                    let placeHolderText = ``;
 | 
			
		||||
                    cm.replaceSelection(placeHolderText);
 | 
			
		||||
 | 
			
		||||
                    let remoteFilename = "image-" + Date.now() + "." + ext;
 | 
			
		||||
                    let formData = new FormData();
 | 
			
		||||
                    formData.append('file', file, remoteFilename);
 | 
			
		||||
 | 
			
		||||
                    window.$http.post('/images/gallery/upload', formData).then(resp => {
 | 
			
		||||
                        replaceContent(placeholderImage, resp.data.thumbs.display);
 | 
			
		||||
                    }).catch(err => {
 | 
			
		||||
                        events.emit('error', trans('errors.image_upload_error'));
 | 
			
		||||
                        replaceContent(placeHolderText, selectedText);
 | 
			
		||||
                        console.log(err);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Show the popup link selector and insert a link when finished
 | 
			
		||||
                function showLinkSelector() {
 | 
			
		||||
                    let cursorPos = cm.getCursor('from');
 | 
			
		||||
                    window.showEntityLinkSelector(entity => {
 | 
			
		||||
                        let selectedText = cm.getSelection() || entity.name;
 | 
			
		||||
                        let newText = `[${selectedText}](${entity.link})`;
 | 
			
		||||
                        cm.focus();
 | 
			
		||||
                        cm.replaceSelection(newText);
 | 
			
		||||
                        cm.setCursor(cursorPos.line, cursorPos.ch + newText.length);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function insertLink() {
 | 
			
		||||
                    let cursorPos = cm.getCursor('from');
 | 
			
		||||
                    let selectedText = cm.getSelection() || '';
 | 
			
		||||
                    let newText = `[${selectedText}]()`;
 | 
			
		||||
                    cm.focus();
 | 
			
		||||
                    cm.replaceSelection(newText);
 | 
			
		||||
                    let cursorPosDiff = (selectedText === '') ? -3 : -1;
 | 
			
		||||
                    cm.setCursor(cursorPos.line, cursorPos.ch + newText.length+cursorPosDiff);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Show the image manager and handle image insertion
 | 
			
		||||
                function showImageManager() {
 | 
			
		||||
                    let cursorPos = cm.getCursor('from');
 | 
			
		||||
                    window.ImageManager.show(image => {
 | 
			
		||||
                        let selectedText = cm.getSelection();
 | 
			
		||||
                        let newText = "";
 | 
			
		||||
                        cm.focus();
 | 
			
		||||
                        cm.replaceSelection(newText);
 | 
			
		||||
                        cm.setCursor(cursorPos.line, cursorPos.ch + newText.length);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Update the data models and rendered output
 | 
			
		||||
                function update(instance) {
 | 
			
		||||
                    let content = instance.getValue();
 | 
			
		||||
                    element.val(content);
 | 
			
		||||
| 
						 | 
				
			
			@ -267,6 +403,9 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
                }
 | 
			
		||||
                update(cm);
 | 
			
		||||
 | 
			
		||||
                // Listen to commands from parent scope
 | 
			
		||||
                scope.$on('md-insert-link', showLinkSelector);
 | 
			
		||||
                scope.$on('md-insert-image', showImageManager);
 | 
			
		||||
                scope.$on('markdown-update', (event, value) => {
 | 
			
		||||
                    cm.setValue(value);
 | 
			
		||||
                    element.val(value);
 | 
			
		||||
| 
						 | 
				
			
			@ -287,8 +426,7 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
            restrict: 'A',
 | 
			
		||||
            link: function (scope, element, attrs) {
 | 
			
		||||
 | 
			
		||||
                // Elements
 | 
			
		||||
                const $input = element.find('[markdown-input] textarea').first();
 | 
			
		||||
                // Editor Elements
 | 
			
		||||
                const $display = element.find('.markdown-display').first();
 | 
			
		||||
                const $insertImage = element.find('button[data-action="insertImage"]');
 | 
			
		||||
                const $insertEntityLink = element.find('button[data-action="insertEntityLink"]');
 | 
			
		||||
| 
						 | 
				
			
			@ -299,11 +437,9 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
                    window.open(this.getAttribute('href'));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                let currentCaretPos = 0;
 | 
			
		||||
 | 
			
		||||
                $input.blur(event => {
 | 
			
		||||
                    currentCaretPos = $input[0].selectionStart;
 | 
			
		||||
                });
 | 
			
		||||
                // Editor UI Actions
 | 
			
		||||
                $insertEntityLink.click(e => {scope.$broadcast('md-insert-link');});
 | 
			
		||||
                $insertImage.click(e => {scope.$broadcast('md-insert-image');});
 | 
			
		||||
 | 
			
		||||
                // Handle scroll sync event from editor scroll
 | 
			
		||||
                $rootScope.$on('markdown-scroll', (event, lineCount) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -315,140 +451,6 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
                        }, {queue: false, duration: 200, easing: 'linear'});
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Editor key-presses
 | 
			
		||||
                $input.keydown(event => {
 | 
			
		||||
                    // Insert image shortcut
 | 
			
		||||
                    if (event.which === 73 && event.ctrlKey && event.shiftKey) {
 | 
			
		||||
                        event.preventDefault();
 | 
			
		||||
                        let caretPos = $input[0].selectionStart;
 | 
			
		||||
                        let currentContent = $input.val();
 | 
			
		||||
                        const mdImageText = "";
 | 
			
		||||
                        $input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
 | 
			
		||||
                        $input.focus();
 | 
			
		||||
                        $input[0].selectionStart = caretPos + (";
 | 
			
		||||
                        $input[0].selectionEnd = caretPos + (';
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Insert entity link shortcut
 | 
			
		||||
                    if (event.which === 75 && event.ctrlKey && event.shiftKey) {
 | 
			
		||||
                        showLinkSelector();
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Pass key presses to controller via event
 | 
			
		||||
                    scope.$emit('editor-keydown', event);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Insert image from image manager
 | 
			
		||||
                $insertImage.click(event => {
 | 
			
		||||
                    window.ImageManager.showExternal(image => {
 | 
			
		||||
                        let caretPos = currentCaretPos;
 | 
			
		||||
                        let currentContent = $input.val();
 | 
			
		||||
                        let mdImageText = "";
 | 
			
		||||
                        $input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
 | 
			
		||||
                        $input.change();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                function showLinkSelector() {
 | 
			
		||||
                    window.showEntityLinkSelector((entity) => {
 | 
			
		||||
                        let selectionStart = currentCaretPos;
 | 
			
		||||
                        let selectionEnd = $input[0].selectionEnd;
 | 
			
		||||
                        let textSelected = (selectionEnd !== selectionStart);
 | 
			
		||||
                        let currentContent = $input.val();
 | 
			
		||||
 | 
			
		||||
                        if (textSelected) {
 | 
			
		||||
                            let selectedText = currentContent.substring(selectionStart, selectionEnd);
 | 
			
		||||
                            let linkText = `[${selectedText}](${entity.link})`;
 | 
			
		||||
                            $input.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionEnd));
 | 
			
		||||
                        } else {
 | 
			
		||||
                            let linkText = ` [${entity.name}](${entity.link}) `;
 | 
			
		||||
                            $input.val(currentContent.substring(0, selectionStart) + linkText + currentContent.substring(selectionStart))
 | 
			
		||||
                        }
 | 
			
		||||
                        $input.change();
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                $insertEntityLink.click(showLinkSelector);
 | 
			
		||||
 | 
			
		||||
                // Upload and insert image on paste
 | 
			
		||||
                function editorPaste(e) {
 | 
			
		||||
                    e = e.originalEvent;
 | 
			
		||||
                    if (!e.clipboardData) return
 | 
			
		||||
                    let items = e.clipboardData.items;
 | 
			
		||||
                    if (!items) return;
 | 
			
		||||
                    for (let i = 0; i < items.length; i++) {
 | 
			
		||||
                        uploadImage(items[i].getAsFile());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $input.on('paste', editorPaste);
 | 
			
		||||
 | 
			
		||||
                // Handle image drop, Uploads images to BookStack.
 | 
			
		||||
                function handleImageDrop(event) {
 | 
			
		||||
                    event.stopPropagation();
 | 
			
		||||
                    event.preventDefault();
 | 
			
		||||
                    let files = event.originalEvent.dataTransfer.files;
 | 
			
		||||
                    for (let i = 0; i < files.length; i++) {
 | 
			
		||||
                        uploadImage(files[i]);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $input.on('drop', handleImageDrop);
 | 
			
		||||
 | 
			
		||||
                // Handle image upload and add image into markdown content
 | 
			
		||||
                function uploadImage(file) {
 | 
			
		||||
                    if (file.type.indexOf('image') !== 0) return;
 | 
			
		||||
                    let formData = new FormData();
 | 
			
		||||
                    let ext = 'png';
 | 
			
		||||
                    let xhr = new XMLHttpRequest();
 | 
			
		||||
 | 
			
		||||
                    if (file.name) {
 | 
			
		||||
                        let fileNameMatches = file.name.match(/\.(.+)$/);
 | 
			
		||||
                        if (fileNameMatches) {
 | 
			
		||||
                            ext = fileNameMatches[1];
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Insert image into markdown
 | 
			
		||||
                    let id = "image-" + Math.random().toString(16).slice(2);
 | 
			
		||||
                    let selectStart = $input[0].selectionStart;
 | 
			
		||||
                    let selectEnd = $input[0].selectionEnd;
 | 
			
		||||
                    let content = $input[0].value;
 | 
			
		||||
                    let selectText = content.substring(selectStart, selectEnd);
 | 
			
		||||
                    let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
 | 
			
		||||
                    let innerContent = ((selectEnd > selectStart) ? `![${selectText}]` : '![]') + `(${placeholderImage})`;
 | 
			
		||||
                    $input[0].value = content.substring(0, selectStart) +  innerContent + content.substring(selectEnd);
 | 
			
		||||
 | 
			
		||||
                    $input.focus();
 | 
			
		||||
                    $input[0].selectionStart = selectStart;
 | 
			
		||||
                    $input[0].selectionEnd = selectStart;
 | 
			
		||||
 | 
			
		||||
                    let remoteFilename = "image-" + Date.now() + "." + ext;
 | 
			
		||||
                    formData.append('file', file, remoteFilename);
 | 
			
		||||
                    formData.append('_token', document.querySelector('meta[name="token"]').getAttribute('content'));
 | 
			
		||||
 | 
			
		||||
                    xhr.open('POST', window.baseUrl('/images/gallery/upload'));
 | 
			
		||||
                    xhr.onload = function () {
 | 
			
		||||
                        let selectStart = $input[0].selectionStart;
 | 
			
		||||
                        if (xhr.status === 200 || xhr.status === 201) {
 | 
			
		||||
                            let result = JSON.parse(xhr.responseText);
 | 
			
		||||
                            $input[0].value = $input[0].value.replace(placeholderImage, result.thumbs.display);
 | 
			
		||||
                            $input.change();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            console.log(trans('errors.image_upload_error'));
 | 
			
		||||
                            console.log(xhr.responseText);
 | 
			
		||||
                            $input[0].value = $input[0].value.replace(innerContent, '');
 | 
			
		||||
                            $input.change();
 | 
			
		||||
                        }
 | 
			
		||||
                        $input.focus();
 | 
			
		||||
                        $input[0].selectionStart = selectStart;
 | 
			
		||||
                        $input[0].selectionEnd = selectStart;
 | 
			
		||||
                    };
 | 
			
		||||
                    xhr.send(formData);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }]);
 | 
			
		||||
| 
						 | 
				
			
			@ -494,188 +496,6 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
        }
 | 
			
		||||
    }]);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Tag Autosuggestions
 | 
			
		||||
     * Listens to child inputs and provides autosuggestions depending on field type
 | 
			
		||||
     * and input. Suggestions provided by server.
 | 
			
		||||
     */
 | 
			
		||||
    ngApp.directive('tagAutosuggestions', ['$http', function ($http) {
 | 
			
		||||
        return {
 | 
			
		||||
            restrict: 'A',
 | 
			
		||||
            link: function (scope, elem, attrs) {
 | 
			
		||||
 | 
			
		||||
                // Local storage for quick caching.
 | 
			
		||||
                const localCache = {};
 | 
			
		||||
 | 
			
		||||
                // Create suggestion element
 | 
			
		||||
                const suggestionBox = document.createElement('ul');
 | 
			
		||||
                suggestionBox.className = 'suggestion-box';
 | 
			
		||||
                suggestionBox.style.position = 'absolute';
 | 
			
		||||
                suggestionBox.style.display = 'none';
 | 
			
		||||
                const $suggestionBox = $(suggestionBox);
 | 
			
		||||
 | 
			
		||||
                // General state tracking
 | 
			
		||||
                let isShowing = false;
 | 
			
		||||
                let currentInput = false;
 | 
			
		||||
                let active = 0;
 | 
			
		||||
 | 
			
		||||
                // Listen to input events on autosuggest fields
 | 
			
		||||
                elem.on('input focus', '[autosuggest]', function (event) {
 | 
			
		||||
                    let $input = $(this);
 | 
			
		||||
                    let val = $input.val();
 | 
			
		||||
                    let url = $input.attr('autosuggest');
 | 
			
		||||
                    let type = $input.attr('autosuggest-type');
 | 
			
		||||
 | 
			
		||||
                    // Add name param to request if for a value
 | 
			
		||||
                    if (type.toLowerCase() === 'value') {
 | 
			
		||||
                        let $nameInput = $input.closest('tr').find('[autosuggest-type="name"]').first();
 | 
			
		||||
                        let nameVal = $nameInput.val();
 | 
			
		||||
                        if (nameVal !== '') {
 | 
			
		||||
                            url += '?name=' + encodeURIComponent(nameVal);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    let suggestionPromise = getSuggestions(val.slice(0, 3), url);
 | 
			
		||||
                    suggestionPromise.then(suggestions => {
 | 
			
		||||
                        if (val.length === 0) {
 | 
			
		||||
                            displaySuggestions($input, suggestions.slice(0, 6));
 | 
			
		||||
                        } else  {
 | 
			
		||||
                            suggestions = suggestions.filter(item => {
 | 
			
		||||
                                return item.toLowerCase().indexOf(val.toLowerCase()) !== -1;
 | 
			
		||||
                            }).slice(0, 4);
 | 
			
		||||
                            displaySuggestions($input, suggestions);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Hide autosuggestions when input loses focus.
 | 
			
		||||
                // Slight delay to allow clicks.
 | 
			
		||||
                let lastFocusTime = 0;
 | 
			
		||||
                elem.on('blur', '[autosuggest]', function (event) {
 | 
			
		||||
                    let startTime = Date.now();
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                        if (lastFocusTime < startTime) {
 | 
			
		||||
                            $suggestionBox.hide();
 | 
			
		||||
                            isShowing = false;
 | 
			
		||||
                        }
 | 
			
		||||
                    }, 200)
 | 
			
		||||
                });
 | 
			
		||||
                elem.on('focus', '[autosuggest]', function (event) {
 | 
			
		||||
                    lastFocusTime = Date.now();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                elem.on('keydown', '[autosuggest]', function (event) {
 | 
			
		||||
                    if (!isShowing) return;
 | 
			
		||||
 | 
			
		||||
                    let suggestionElems = suggestionBox.childNodes;
 | 
			
		||||
                    let suggestCount = suggestionElems.length;
 | 
			
		||||
 | 
			
		||||
                    // Down arrow
 | 
			
		||||
                    if (event.keyCode === 40) {
 | 
			
		||||
                        let newActive = (active === suggestCount - 1) ? 0 : active + 1;
 | 
			
		||||
                        changeActiveTo(newActive, suggestionElems);
 | 
			
		||||
                    }
 | 
			
		||||
                    // Up arrow
 | 
			
		||||
                    else if (event.keyCode === 38) {
 | 
			
		||||
                        let newActive = (active === 0) ? suggestCount - 1 : active - 1;
 | 
			
		||||
                        changeActiveTo(newActive, suggestionElems);
 | 
			
		||||
                    }
 | 
			
		||||
                    // Enter or tab key
 | 
			
		||||
                    else if ((event.keyCode === 13 || event.keyCode === 9) && !event.shiftKey) {
 | 
			
		||||
                        currentInput[0].value = suggestionElems[active].textContent;
 | 
			
		||||
                        currentInput.focus();
 | 
			
		||||
                        $suggestionBox.hide();
 | 
			
		||||
                        isShowing = false;
 | 
			
		||||
                        if (event.keyCode === 13) {
 | 
			
		||||
                            event.preventDefault();
 | 
			
		||||
                            return false;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Change the active suggestion to the given index
 | 
			
		||||
                function changeActiveTo(index, suggestionElems) {
 | 
			
		||||
                    suggestionElems[active].className = '';
 | 
			
		||||
                    active = index;
 | 
			
		||||
                    suggestionElems[active].className = 'active';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Display suggestions on a field
 | 
			
		||||
                let prevSuggestions = [];
 | 
			
		||||
 | 
			
		||||
                function displaySuggestions($input, suggestions) {
 | 
			
		||||
 | 
			
		||||
                    // Hide if no suggestions
 | 
			
		||||
                    if (suggestions.length === 0) {
 | 
			
		||||
                        $suggestionBox.hide();
 | 
			
		||||
                        isShowing = false;
 | 
			
		||||
                        prevSuggestions = suggestions;
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Otherwise show and attach to input
 | 
			
		||||
                    if (!isShowing) {
 | 
			
		||||
                        $suggestionBox.show();
 | 
			
		||||
                        isShowing = true;
 | 
			
		||||
                    }
 | 
			
		||||
                    if ($input !== currentInput) {
 | 
			
		||||
                        $suggestionBox.detach();
 | 
			
		||||
                        $input.after($suggestionBox);
 | 
			
		||||
                        currentInput = $input;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Return if no change
 | 
			
		||||
                    if (prevSuggestions.join() === suggestions.join()) {
 | 
			
		||||
                        prevSuggestions = suggestions;
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Build suggestions
 | 
			
		||||
                    $suggestionBox[0].innerHTML = '';
 | 
			
		||||
                    for (let i = 0; i < suggestions.length; i++) {
 | 
			
		||||
                        let suggestion = document.createElement('li');
 | 
			
		||||
                        suggestion.textContent = suggestions[i];
 | 
			
		||||
                        suggestion.onclick = suggestionClick;
 | 
			
		||||
                        if (i === 0) {
 | 
			
		||||
                            suggestion.className = 'active';
 | 
			
		||||
                            active = 0;
 | 
			
		||||
                        }
 | 
			
		||||
                        $suggestionBox[0].appendChild(suggestion);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    prevSuggestions = suggestions;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Suggestion click event
 | 
			
		||||
                function suggestionClick(event) {
 | 
			
		||||
                    currentInput[0].value = this.textContent;
 | 
			
		||||
                    currentInput.focus();
 | 
			
		||||
                    $suggestionBox.hide();
 | 
			
		||||
                    isShowing = false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Get suggestions & cache
 | 
			
		||||
                function getSuggestions(input, url) {
 | 
			
		||||
                    let hasQuery = url.indexOf('?') !== -1;
 | 
			
		||||
                    let searchUrl = url + (hasQuery ? '&' : '?') + 'search=' + encodeURIComponent(input);
 | 
			
		||||
 | 
			
		||||
                    // Get from local cache if exists
 | 
			
		||||
                    if (typeof localCache[searchUrl] !== 'undefined') {
 | 
			
		||||
                        return new Promise((resolve, reject) => {
 | 
			
		||||
                            resolve(localCache[searchUrl]);
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return $http.get(searchUrl).then(response => {
 | 
			
		||||
                        localCache[searchUrl] = response.data;
 | 
			
		||||
                        return response.data;
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }]);
 | 
			
		||||
 | 
			
		||||
    ngApp.directive('entityLinkSelector', [function($http) {
 | 
			
		||||
        return {
 | 
			
		||||
            restrict: 'A',
 | 
			
		||||
| 
						 | 
				
			
			@ -711,6 +531,7 @@ module.exports = function (ngApp, events) {
 | 
			
		|||
                function hide() {
 | 
			
		||||
                    element.fadeOut(240);
 | 
			
		||||
                }
 | 
			
		||||
                scope.hide = hide;
 | 
			
		||||
 | 
			
		||||
                // Listen to confirmation of entity selections (doubleclick)
 | 
			
		||||
                events.listen('entity-select-confirm', entity => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
require("babel-polyfill");
 | 
			
		||||
 | 
			
		||||
// Url retrieval function
 | 
			
		||||
window.baseUrl = function(path) {
 | 
			
		||||
| 
						 | 
				
			
			@ -17,11 +18,9 @@ let axiosInstance = axios.create({
 | 
			
		|||
        'baseURL': window.baseUrl('')
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
window.$http = axiosInstance;
 | 
			
		||||
Vue.prototype.$http = axiosInstance;
 | 
			
		||||
 | 
			
		||||
require("./vues/vues");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// AngularJS - Create application and load components
 | 
			
		||||
const angular = require("angular");
 | 
			
		||||
| 
						 | 
				
			
			@ -64,11 +63,12 @@ class EventManager {
 | 
			
		|||
window.Events = new EventManager();
 | 
			
		||||
Vue.prototype.$events = window.Events;
 | 
			
		||||
 | 
			
		||||
require("./vues/vues");
 | 
			
		||||
require("./components");
 | 
			
		||||
 | 
			
		||||
// Load in angular specific items
 | 
			
		||||
const Services = require('./services');
 | 
			
		||||
const Directives = require('./directives');
 | 
			
		||||
const Controllers = require('./controllers');
 | 
			
		||||
Services(ngApp, window.Events);
 | 
			
		||||
Directives(ngApp, window.Events);
 | 
			
		||||
Controllers(ngApp, window.Events);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -90,83 +90,11 @@ jQuery.expr[":"].contains = $.expr.createPseudo(function (arg) {
 | 
			
		|||
    };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Global jQuery Elements
 | 
			
		||||
let notifications = $('.notification');
 | 
			
		||||
let successNotification = notifications.filter('.pos');
 | 
			
		||||
let errorNotification = notifications.filter('.neg');
 | 
			
		||||
let warningNotification = notifications.filter('.warning');
 | 
			
		||||
// Notification Events
 | 
			
		||||
window.Events.listen('success', function (text) {
 | 
			
		||||
    successNotification.hide();
 | 
			
		||||
    successNotification.find('span').text(text);
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        successNotification.show();
 | 
			
		||||
    }, 1);
 | 
			
		||||
});
 | 
			
		||||
window.Events.listen('warning', function (text) {
 | 
			
		||||
    warningNotification.find('span').text(text);
 | 
			
		||||
    warningNotification.show();
 | 
			
		||||
});
 | 
			
		||||
window.Events.listen('error', function (text) {
 | 
			
		||||
    errorNotification.find('span').text(text);
 | 
			
		||||
    errorNotification.show();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Notification hiding
 | 
			
		||||
notifications.click(function () {
 | 
			
		||||
    $(this).fadeOut(100);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Chapter page list toggles
 | 
			
		||||
$('.chapter-toggle').click(function (e) {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    $(this).toggleClass('open');
 | 
			
		||||
    $(this).closest('.chapter').find('.inset-list').slideToggle(180);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Back to top button
 | 
			
		||||
$('#back-to-top').click(function() {
 | 
			
		||||
     $('#header').smoothScrollTo();
 | 
			
		||||
});
 | 
			
		||||
let scrollTopShowing = false;
 | 
			
		||||
let scrollTop = document.getElementById('back-to-top');
 | 
			
		||||
let scrollTopBreakpoint = 1200;
 | 
			
		||||
window.addEventListener('scroll', function() {
 | 
			
		||||
    let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0;
 | 
			
		||||
    if (!scrollTopShowing && scrollTopPos > scrollTopBreakpoint) {
 | 
			
		||||
        scrollTop.style.display = 'block';
 | 
			
		||||
        scrollTopShowing = true;
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            scrollTop.style.opacity = 0.4;
 | 
			
		||||
        }, 1);
 | 
			
		||||
    } else if (scrollTopShowing && scrollTopPos < scrollTopBreakpoint) {
 | 
			
		||||
        scrollTop.style.opacity = 0;
 | 
			
		||||
        scrollTopShowing = false;
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            scrollTop.style.display = 'none';
 | 
			
		||||
        }, 500);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Common jQuery actions
 | 
			
		||||
$('[data-action="expand-entity-list-details"]').click(function() {
 | 
			
		||||
    $('.entity-list.compact').find('p').not('.empty-text').slideToggle(240);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Popup close
 | 
			
		||||
$('.popup-close').click(function() {
 | 
			
		||||
    $(this).closest('.overlay').fadeOut(240);
 | 
			
		||||
});
 | 
			
		||||
$('.overlay').click(function(event) {
 | 
			
		||||
    if (!$(event.target).hasClass('overlay')) return;
 | 
			
		||||
    $(this).fadeOut(240);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Detect IE for css
 | 
			
		||||
if(navigator.userAgent.indexOf('MSIE')!==-1
 | 
			
		||||
    || navigator.appVersion.indexOf('Trident/') > 0
 | 
			
		||||
    || navigator.userAgent.indexOf('Safari') !== -1){
 | 
			
		||||
    $('body').addClass('flexbox-support');
 | 
			
		||||
    document.body.classList.add('flexbox-support');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Page specific items
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,7 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
const Code = require('../code');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handle pasting images from clipboard.
 | 
			
		||||
 * @param e  - event
 | 
			
		||||
| 
						 | 
				
			
			@ -50,23 +52,183 @@ function editorPaste(e, editor) {
 | 
			
		|||
function registerEditorShortcuts(editor) {
 | 
			
		||||
    // Headers
 | 
			
		||||
    for (let i = 1; i < 5; i++) {
 | 
			
		||||
        editor.addShortcut('meta+' + i, '', ['FormatBlock', false, 'h' + i]);
 | 
			
		||||
        editor.shortcuts.add('meta+' + i, '', ['FormatBlock', false, 'h' + (i+1)]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Other block shortcuts
 | 
			
		||||
    editor.addShortcut('meta+q', '', ['FormatBlock', false, 'blockquote']);
 | 
			
		||||
    editor.addShortcut('meta+d', '', ['FormatBlock', false, 'p']);
 | 
			
		||||
    editor.addShortcut('meta+e', '', ['FormatBlock', false, 'pre']);
 | 
			
		||||
    editor.addShortcut('meta+shift+E', '', ['FormatBlock', false, 'code']);
 | 
			
		||||
    editor.shortcuts.add('meta+5', '', ['FormatBlock', false, 'p']);
 | 
			
		||||
    editor.shortcuts.add('meta+d', '', ['FormatBlock', false, 'p']);
 | 
			
		||||
    editor.shortcuts.add('meta+6', '', ['FormatBlock', false, 'blockquote']);
 | 
			
		||||
    editor.shortcuts.add('meta+q', '', ['FormatBlock', false, 'blockquote']);
 | 
			
		||||
    editor.shortcuts.add('meta+7', '', ['codeeditor', false, 'pre']);
 | 
			
		||||
    editor.shortcuts.add('meta+e', '', ['codeeditor', false, 'pre']);
 | 
			
		||||
    editor.shortcuts.add('meta+8', '', ['FormatBlock', false, 'code']);
 | 
			
		||||
    editor.shortcuts.add('meta+shift+E', '', ['FormatBlock', false, 'code']);
 | 
			
		||||
    // Loop through callout styles
 | 
			
		||||
    editor.shortcuts.add('meta+9', '', function() {
 | 
			
		||||
        let selectedNode = editor.selection.getNode();
 | 
			
		||||
        let formats = ['info', 'success', 'warning', 'danger'];
 | 
			
		||||
 | 
			
		||||
        if (!selectedNode || selectedNode.className.indexOf('callout') === -1) {
 | 
			
		||||
            editor.formatter.apply('calloutinfo');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < formats.length; i++) {
 | 
			
		||||
            if (selectedNode.className.indexOf(formats[i]) === -1) continue;
 | 
			
		||||
            let newFormat = (i === formats.length -1) ? formats[0] : formats[i+1];
 | 
			
		||||
            editor.formatter.apply('callout' + newFormat);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        editor.formatter.apply('p');
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Create and enable our custom code plugin
 | 
			
		||||
 */
 | 
			
		||||
function codePlugin() {
 | 
			
		||||
 | 
			
		||||
    function elemIsCodeBlock(elem) {
 | 
			
		||||
        return elem.className === 'CodeMirrorContainer';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function showPopup(editor) {
 | 
			
		||||
        let selectedNode = editor.selection.getNode();
 | 
			
		||||
 | 
			
		||||
        if (!elemIsCodeBlock(selectedNode)) {
 | 
			
		||||
            let providedCode = editor.selection.getNode().textContent;
 | 
			
		||||
            window.vues['code-editor'].open(providedCode, '', (code, lang) => {
 | 
			
		||||
                let wrap = document.createElement('div');
 | 
			
		||||
                wrap.innerHTML = `<pre><code class="language-${lang}"></code></pre>`;
 | 
			
		||||
                wrap.querySelector('code').innerText = code;
 | 
			
		||||
 | 
			
		||||
                editor.formatter.toggle('pre');
 | 
			
		||||
                let node = editor.selection.getNode();
 | 
			
		||||
                editor.dom.setHTML(node, wrap.querySelector('pre').innerHTML);
 | 
			
		||||
                editor.fire('SetContent');
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let lang = selectedNode.hasAttribute('data-lang') ? selectedNode.getAttribute('data-lang') : '';
 | 
			
		||||
        let currentCode = selectedNode.querySelector('textarea').textContent;
 | 
			
		||||
 | 
			
		||||
        window.vues['code-editor'].open(currentCode, lang, (code, lang) => {
 | 
			
		||||
            let editorElem = selectedNode.querySelector('.CodeMirror');
 | 
			
		||||
            let cmInstance = editorElem.CodeMirror;
 | 
			
		||||
            if (cmInstance) {
 | 
			
		||||
                Code.setContent(cmInstance, code);
 | 
			
		||||
                Code.setMode(cmInstance, lang);
 | 
			
		||||
            }
 | 
			
		||||
            let textArea = selectedNode.querySelector('textarea');
 | 
			
		||||
            if (textArea) textArea.textContent = code;
 | 
			
		||||
            selectedNode.setAttribute('data-lang', lang);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function codeMirrorContainerToPre($codeMirrorContainer) {
 | 
			
		||||
        let textArea = $codeMirrorContainer[0].querySelector('textarea');
 | 
			
		||||
        let code = textArea.textContent;
 | 
			
		||||
        let lang = $codeMirrorContainer[0].getAttribute('data-lang');
 | 
			
		||||
 | 
			
		||||
        $codeMirrorContainer.removeAttr('contentEditable');
 | 
			
		||||
        let $pre = $('<pre></pre>');
 | 
			
		||||
        $pre.append($('<code></code>').each((index, elem) => {
 | 
			
		||||
            // Needs to be textContent since innerText produces BR:s
 | 
			
		||||
            elem.textContent = code;
 | 
			
		||||
        }).attr('class', `language-${lang}`));
 | 
			
		||||
        $codeMirrorContainer.replaceWith($pre);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    window.tinymce.PluginManager.add('codeeditor', function(editor, url) {
 | 
			
		||||
 | 
			
		||||
        let $ = editor.$;
 | 
			
		||||
 | 
			
		||||
        editor.addButton('codeeditor', {
 | 
			
		||||
            text: 'Code block',
 | 
			
		||||
            icon: false,
 | 
			
		||||
            cmd: 'codeeditor'
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        editor.addCommand('codeeditor', () => {
 | 
			
		||||
            showPopup(editor);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Convert
 | 
			
		||||
        editor.on('PreProcess', function (e) {
 | 
			
		||||
            $('div.CodeMirrorContainer', e.node).
 | 
			
		||||
            each((index, elem) => {
 | 
			
		||||
                let $elem = $(elem);
 | 
			
		||||
                codeMirrorContainerToPre($elem);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        editor.on('dblclick', event => {
 | 
			
		||||
            let selectedNode = editor.selection.getNode();
 | 
			
		||||
            if (!elemIsCodeBlock(selectedNode)) return;
 | 
			
		||||
            showPopup(editor);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        editor.on('SetContent', function () {
 | 
			
		||||
 | 
			
		||||
            // Recover broken codemirror instances
 | 
			
		||||
            $('.CodeMirrorContainer').filter((index ,elem) => {
 | 
			
		||||
                return typeof elem.querySelector('.CodeMirror').CodeMirror === 'undefined';
 | 
			
		||||
            }).each((index, elem) => {
 | 
			
		||||
                codeMirrorContainerToPre($(elem));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            let codeSamples = $('body > pre').filter((index, elem) => {
 | 
			
		||||
                return elem.contentEditable !== "false";
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (!codeSamples.length) return;
 | 
			
		||||
            editor.undoManager.transact(function () {
 | 
			
		||||
                codeSamples.each((index, elem) => {
 | 
			
		||||
                    Code.wysiwygView(elem);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hrPlugin() {
 | 
			
		||||
    window.tinymce.PluginManager.add('customhr', function (editor) {
 | 
			
		||||
        editor.addCommand('InsertHorizontalRule', function () {
 | 
			
		||||
            let hrElem = document.createElement('hr');
 | 
			
		||||
            let cNode = editor.selection.getNode();
 | 
			
		||||
            let parentNode = cNode.parentNode;
 | 
			
		||||
            parentNode.insertBefore(hrElem, cNode);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        editor.addButton('hr', {
 | 
			
		||||
            icon: 'hr',
 | 
			
		||||
            tooltip: 'Horizontal line',
 | 
			
		||||
            cmd: 'InsertHorizontalRule'
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        editor.addMenuItem('hr', {
 | 
			
		||||
            icon: 'hr',
 | 
			
		||||
            text: 'Horizontal line',
 | 
			
		||||
            cmd: 'InsertHorizontalRule',
 | 
			
		||||
            context: 'insert'
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = function() {
 | 
			
		||||
    hrPlugin();
 | 
			
		||||
    codePlugin();
 | 
			
		||||
    let settings = {
 | 
			
		||||
        selector: '#html-editor',
 | 
			
		||||
        content_css: [
 | 
			
		||||
            window.baseUrl('/css/styles.css'),
 | 
			
		||||
            window.baseUrl('/libs/material-design-iconic-font/css/material-design-iconic-font.min.css')
 | 
			
		||||
        ],
 | 
			
		||||
        branding: false,
 | 
			
		||||
        body_class: 'page-content',
 | 
			
		||||
        browser_spellcheck: true,
 | 
			
		||||
        relative_urls: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -77,10 +239,10 @@ module.exports = function() {
 | 
			
		|||
        paste_data_images: false,
 | 
			
		||||
        extended_valid_elements: 'pre[*]',
 | 
			
		||||
        automatic_uploads: false,
 | 
			
		||||
        valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
 | 
			
		||||
        plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codesample",
 | 
			
		||||
        valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre]",
 | 
			
		||||
        plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor",
 | 
			
		||||
        imagetools_toolbar: 'imageoptions',
 | 
			
		||||
        toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen codesample",
 | 
			
		||||
        toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen",
 | 
			
		||||
        content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}",
 | 
			
		||||
        style_formats: [
 | 
			
		||||
            {title: "Header Large", format: "h2"},
 | 
			
		||||
| 
						 | 
				
			
			@ -89,20 +251,25 @@ module.exports = function() {
 | 
			
		|||
            {title: "Header Tiny", format: "h5"},
 | 
			
		||||
            {title: "Paragraph", format: "p", exact: true, classes: ''},
 | 
			
		||||
            {title: "Blockquote", format: "blockquote"},
 | 
			
		||||
            {title: "Code Block", icon: "code", format: "pre"},
 | 
			
		||||
            {title: "Code Block", icon: "code", cmd: 'codeeditor', format: 'codeeditor'},
 | 
			
		||||
            {title: "Inline Code", icon: "code", inline: "code"},
 | 
			
		||||
            {title: "Callouts", items: [
 | 
			
		||||
                {title: "Success", block: 'p', exact: true, attributes : {'class' : 'callout success'}},
 | 
			
		||||
                {title: "Info", block: 'p', exact: true, attributes : {'class' : 'callout info'}},
 | 
			
		||||
                {title: "Warning", block: 'p', exact: true, attributes : {'class' : 'callout warning'}},
 | 
			
		||||
                {title: "Danger", block: 'p', exact: true, attributes : {'class' : 'callout danger'}}
 | 
			
		||||
            ]}
 | 
			
		||||
                {title: "Info", format: 'calloutinfo'},
 | 
			
		||||
                {title: "Success", format: 'calloutsuccess'},
 | 
			
		||||
                {title: "Warning", format: 'calloutwarning'},
 | 
			
		||||
                {title: "Danger", format: 'calloutdanger'}
 | 
			
		||||
            ]},
 | 
			
		||||
        ],
 | 
			
		||||
        style_formats_merge: false,
 | 
			
		||||
        formats: {
 | 
			
		||||
            codeeditor: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div'},
 | 
			
		||||
            alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-left'},
 | 
			
		||||
            aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-center'},
 | 
			
		||||
            alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
 | 
			
		||||
            calloutsuccess: {block: 'p', exact: true, attributes: {class: 'callout success'}},
 | 
			
		||||
            calloutinfo: {block: 'p', exact: true, attributes: {class: 'callout info'}},
 | 
			
		||||
            calloutwarning: {block: 'p', exact: true, attributes: {class: 'callout warning'}},
 | 
			
		||||
            calloutdanger: {block: 'p', exact: true, attributes: {class: 'callout danger'}}
 | 
			
		||||
        },
 | 
			
		||||
        file_browser_callback: function (field_name, url, type, win) {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -116,7 +283,7 @@ module.exports = function() {
 | 
			
		|||
 | 
			
		||||
            if (type === 'image') {
 | 
			
		||||
                // Show image manager
 | 
			
		||||
                window.ImageManager.showExternal(function (image) {
 | 
			
		||||
                window.ImageManager.show(function (image) {
 | 
			
		||||
 | 
			
		||||
                    // Set popover link input to image url then fire change event
 | 
			
		||||
                    // to ensure the new value sticks
 | 
			
		||||
| 
						 | 
				
			
			@ -198,7 +365,7 @@ module.exports = function() {
 | 
			
		|||
                icon: 'image',
 | 
			
		||||
                tooltip: 'Insert an image',
 | 
			
		||||
                onclick: function () {
 | 
			
		||||
                    window.ImageManager.showExternal(function (image) {
 | 
			
		||||
                    window.ImageManager.show(function (image) {
 | 
			
		||||
                        let html = `<a href="${image.url}" target="_blank">`;
 | 
			
		||||
                        html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
 | 
			
		||||
                        html += '</a>';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,3 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
// Configure ZeroClipboard
 | 
			
		||||
const Clipboard = require("clipboard");
 | 
			
		||||
const Code = require('../code');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
module.exports = function(ngApp, events) {
 | 
			
		||||
 | 
			
		||||
    ngApp.factory('imageManagerService', function() {
 | 
			
		||||
        return {
 | 
			
		||||
            show: false,
 | 
			
		||||
            showExternal: false
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
const codeLib = require('../code');
 | 
			
		||||
 | 
			
		||||
const methods = {
 | 
			
		||||
    show() {
 | 
			
		||||
        if (!this.editor) this.editor = codeLib.popupEditor(this.$refs.editor, this.language);
 | 
			
		||||
        this.$refs.overlay.style.display = 'flex';
 | 
			
		||||
    },
 | 
			
		||||
    hide() {
 | 
			
		||||
        this.$refs.overlay.style.display = 'none';
 | 
			
		||||
    },
 | 
			
		||||
    updateEditorMode(language) {
 | 
			
		||||
        codeLib.setMode(this.editor, language);
 | 
			
		||||
    },
 | 
			
		||||
    updateLanguage(lang) {
 | 
			
		||||
        this.language = lang;
 | 
			
		||||
        this.updateEditorMode(lang);
 | 
			
		||||
    },
 | 
			
		||||
    open(code, language, callback) {
 | 
			
		||||
        this.show();
 | 
			
		||||
        this.updateEditorMode(language);
 | 
			
		||||
        this.language = language;
 | 
			
		||||
        codeLib.setContent(this.editor, code);
 | 
			
		||||
        this.code = code;
 | 
			
		||||
        this.callback = callback;
 | 
			
		||||
    },
 | 
			
		||||
    save() {
 | 
			
		||||
        if (!this.callback) return;
 | 
			
		||||
        this.callback(this.editor.getValue(), this.language);
 | 
			
		||||
        this.hide();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const data = {
 | 
			
		||||
    editor: null,
 | 
			
		||||
    language: '',
 | 
			
		||||
    code: '',
 | 
			
		||||
    callback: null
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    methods,
 | 
			
		||||
    data
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,130 @@
 | 
			
		|||
 | 
			
		||||
const template = `
 | 
			
		||||
    <div>
 | 
			
		||||
        <input :value="value" :autosuggest-type="type" ref="input"
 | 
			
		||||
            :placeholder="placeholder" :name="name"
 | 
			
		||||
            @input="inputUpdate($event.target.value)" @focus="inputUpdate($event.target.value)"
 | 
			
		||||
            @blur="inputBlur"
 | 
			
		||||
            @keydown="inputKeydown"
 | 
			
		||||
        />
 | 
			
		||||
        <ul class="suggestion-box" v-if="showSuggestions">
 | 
			
		||||
            <li v-for="(suggestion, i) in suggestions"
 | 
			
		||||
                @click="selectSuggestion(suggestion)"
 | 
			
		||||
                :class="{active: (i === active)}">{{suggestion}}</li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
function data() {
 | 
			
		||||
    return {
 | 
			
		||||
        suggestions: [],
 | 
			
		||||
        showSuggestions: false,
 | 
			
		||||
        active: 0,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ajaxCache = {};
 | 
			
		||||
 | 
			
		||||
const props = ['url', 'type', 'value', 'placeholder', 'name'];
 | 
			
		||||
 | 
			
		||||
function getNameInputVal(valInput) {
 | 
			
		||||
    let parentRow = valInput.parentNode.parentNode;
 | 
			
		||||
    let nameInput = parentRow.querySelector('[autosuggest-type="name"]');
 | 
			
		||||
    return (nameInput === null) ? '' : nameInput.value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const methods = {
 | 
			
		||||
 | 
			
		||||
    inputUpdate(inputValue) {
 | 
			
		||||
        this.$emit('input', inputValue);
 | 
			
		||||
        let params = {};
 | 
			
		||||
 | 
			
		||||
        if (this.type === 'value') {
 | 
			
		||||
            let nameVal = getNameInputVal(this.$el);
 | 
			
		||||
            if (nameVal !== "") params.name = nameVal;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.getSuggestions(inputValue.slice(0, 3), params).then(suggestions => {
 | 
			
		||||
            if (inputValue.length === 0) {
 | 
			
		||||
                this.displaySuggestions(suggestions.slice(0, 6));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            // Filter to suggestions containing searched term
 | 
			
		||||
            suggestions = suggestions.filter(item => {
 | 
			
		||||
                return item.toLowerCase().indexOf(inputValue.toLowerCase()) !== -1;
 | 
			
		||||
            }).slice(0, 4);
 | 
			
		||||
            this.displaySuggestions(suggestions);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    inputBlur() {
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            this.$emit('blur');
 | 
			
		||||
            this.showSuggestions = false;
 | 
			
		||||
        }, 100);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    inputKeydown(event) {
 | 
			
		||||
        if (event.keyCode === 13) event.preventDefault();
 | 
			
		||||
        if (!this.showSuggestions) return;
 | 
			
		||||
 | 
			
		||||
        // Down arrow
 | 
			
		||||
        if (event.keyCode === 40) {
 | 
			
		||||
            this.active = (this.active === this.suggestions.length - 1) ? 0 : this.active+1;
 | 
			
		||||
        }
 | 
			
		||||
        // Up Arrow
 | 
			
		||||
        else if (event.keyCode === 38) {
 | 
			
		||||
            this.active = (this.active === 0) ? this.suggestions.length - 1 : this.active-1;
 | 
			
		||||
        }
 | 
			
		||||
        // Enter or tab keys
 | 
			
		||||
        else if ((event.keyCode === 13 || event.keyCode === 9) && !event.shiftKey) {
 | 
			
		||||
            this.selectSuggestion(this.suggestions[this.active]);
 | 
			
		||||
        }
 | 
			
		||||
        // Escape key
 | 
			
		||||
        else if (event.keyCode === 27) {
 | 
			
		||||
            this.showSuggestions = false;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    displaySuggestions(suggestions) {
 | 
			
		||||
        if (suggestions.length === 0) {
 | 
			
		||||
            this.suggestions = [];
 | 
			
		||||
            this.showSuggestions = false;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.suggestions = suggestions;
 | 
			
		||||
        this.showSuggestions = true;
 | 
			
		||||
        this.active = 0;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    selectSuggestion(suggestion) {
 | 
			
		||||
        this.$refs.input.value = suggestion;
 | 
			
		||||
        this.$refs.input.focus();
 | 
			
		||||
        this.$emit('input', suggestion);
 | 
			
		||||
        this.showSuggestions = false;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get suggestions from BookStack. Store and use local cache if already searched.
 | 
			
		||||
     * @param {String} input
 | 
			
		||||
     * @param {Object} params
 | 
			
		||||
     */
 | 
			
		||||
    getSuggestions(input, params) {
 | 
			
		||||
        params.search = input;
 | 
			
		||||
        let cacheKey = `${this.url}:${JSON.stringify(params)}`;
 | 
			
		||||
 | 
			
		||||
        if (typeof ajaxCache[cacheKey] !== "undefined") return Promise.resolve(ajaxCache[cacheKey]);
 | 
			
		||||
 | 
			
		||||
        return this.$http.get(this.url, {params}).then(resp => {
 | 
			
		||||
            ajaxCache[cacheKey] = resp.data;
 | 
			
		||||
            return resp.data;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const computed = [];
 | 
			
		||||
 | 
			
		||||
module.exports = {template, data, props, methods, computed};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,60 @@
 | 
			
		|||
const DropZone = require("dropzone");
 | 
			
		||||
 | 
			
		||||
const template = `
 | 
			
		||||
    <div class="dropzone-container">
 | 
			
		||||
        <div class="dz-message">{{placeholder}}</div>
 | 
			
		||||
    </div>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const props = ['placeholder', 'uploadUrl', 'uploadedTo'];
 | 
			
		||||
 | 
			
		||||
// TODO - Remove jQuery usage
 | 
			
		||||
function mounted() {
 | 
			
		||||
   let container = this.$el;
 | 
			
		||||
   let _this = this;
 | 
			
		||||
   new DropZone(container, {
 | 
			
		||||
        url: function() {
 | 
			
		||||
            return _this.uploadUrl;
 | 
			
		||||
        },
 | 
			
		||||
        init: function () {
 | 
			
		||||
            let dz = this;
 | 
			
		||||
 | 
			
		||||
            dz.on('sending', function (file, xhr, data) {
 | 
			
		||||
                let token = window.document.querySelector('meta[name=token]').getAttribute('content');
 | 
			
		||||
                data.append('_token', token);
 | 
			
		||||
                let uploadedTo = typeof _this.uploadedTo === 'undefined' ? 0 : _this.uploadedTo;
 | 
			
		||||
                data.append('uploaded_to', uploadedTo);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            dz.on('success', function (file, data) {
 | 
			
		||||
                _this.$emit('success', {file, data});
 | 
			
		||||
                $(file.previewElement).fadeOut(400, function () {
 | 
			
		||||
                    dz.removeFile(file);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            dz.on('error', function (file, errorMessage, xhr) {
 | 
			
		||||
                _this.$emit('error', {file, errorMessage, xhr});
 | 
			
		||||
                console.log(errorMessage);
 | 
			
		||||
                console.log(xhr);
 | 
			
		||||
                function setMessage(message) {
 | 
			
		||||
                    $(file.previewElement).find('[data-dz-errormessage]').text(message);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (xhr.status === 413) setMessage(trans('errors.server_upload_limit'));
 | 
			
		||||
                if (errorMessage.file) setMessage(errorMessage.file[0]);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
   });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function data() {
 | 
			
		||||
    return {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    template,
 | 
			
		||||
    props,
 | 
			
		||||
    mounted,
 | 
			
		||||
    data,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,182 @@
 | 
			
		|||
const dropzone = require('./components/dropzone');
 | 
			
		||||
 | 
			
		||||
let page = 0;
 | 
			
		||||
let previousClickTime = 0;
 | 
			
		||||
let previousClickImage = 0;
 | 
			
		||||
let dataLoaded = false;
 | 
			
		||||
let callback = false;
 | 
			
		||||
let baseUrl = '';
 | 
			
		||||
 | 
			
		||||
let preSearchImages = [];
 | 
			
		||||
let preSearchHasMore = false;
 | 
			
		||||
 | 
			
		||||
const data = {
 | 
			
		||||
    images: [],
 | 
			
		||||
 | 
			
		||||
    imageType: false,
 | 
			
		||||
    uploadedTo: false,
 | 
			
		||||
 | 
			
		||||
    selectedImage: false,
 | 
			
		||||
    dependantPages: false,
 | 
			
		||||
    showing: false,
 | 
			
		||||
    view: 'all',
 | 
			
		||||
    hasMore: false,
 | 
			
		||||
    searching: false,
 | 
			
		||||
    searchTerm: '',
 | 
			
		||||
 | 
			
		||||
    imageUpdateSuccess: false,
 | 
			
		||||
    imageDeleteSuccess: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const methods = {
 | 
			
		||||
 | 
			
		||||
    show(providedCallback) {
 | 
			
		||||
        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;
 | 
			
		||||
        this.fetchData();
 | 
			
		||||
        dataLoaded = true;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    hide() {
 | 
			
		||||
        this.showing = false;
 | 
			
		||||
        this.$el.children[0].components.overlay.hide();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    fetchData() {
 | 
			
		||||
        let url = baseUrl + page;
 | 
			
		||||
        let query = {};
 | 
			
		||||
        if (this.uploadedTo !== false) query.page_id = this.uploadedTo;
 | 
			
		||||
        if (this.searching) query.term = this.searchTerm;
 | 
			
		||||
 | 
			
		||||
        this.$http.get(url, {params: query}).then(response => {
 | 
			
		||||
            this.images = this.images.concat(response.data.images);
 | 
			
		||||
            this.hasMore = response.data.hasMore;
 | 
			
		||||
            page++;
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    setView(viewName) {
 | 
			
		||||
        this.cancelSearch();
 | 
			
		||||
        this.images = [];
 | 
			
		||||
        this.hasMore = false;
 | 
			
		||||
        page = 0;
 | 
			
		||||
        this.view = viewName;
 | 
			
		||||
        baseUrl = window.baseUrl(`/images/${this.imageType}/${viewName}/`);
 | 
			
		||||
        this.fetchData();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    searchImages() {
 | 
			
		||||
        if (this.searchTerm === '') return this.cancelSearch();
 | 
			
		||||
 | 
			
		||||
        // Cache current settings for later
 | 
			
		||||
        if (!this.searching) {
 | 
			
		||||
            preSearchImages = this.images;
 | 
			
		||||
            preSearchHasMore = this.hasMore;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.searching = true;
 | 
			
		||||
        this.images = [];
 | 
			
		||||
        this.hasMore = false;
 | 
			
		||||
        page = 0;
 | 
			
		||||
        baseUrl = window.baseUrl(`/images/${this.imageType}/search/`);
 | 
			
		||||
        this.fetchData();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    cancelSearch() {
 | 
			
		||||
        this.searching = false;
 | 
			
		||||
        this.searchTerm = '';
 | 
			
		||||
        this.images = preSearchImages;
 | 
			
		||||
        this.hasMore = preSearchHasMore;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    imageSelect(image) {
 | 
			
		||||
        let dblClickTime = 300;
 | 
			
		||||
        let currentTime = Date.now();
 | 
			
		||||
        let timeDiff = currentTime - previousClickTime;
 | 
			
		||||
        let isDblClick = timeDiff < dblClickTime && image.id === previousClickImage;
 | 
			
		||||
 | 
			
		||||
        if (isDblClick) {
 | 
			
		||||
            this.callbackAndHide(image);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.selectedImage = image;
 | 
			
		||||
            this.dependantPages = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        previousClickTime = currentTime;
 | 
			
		||||
        previousClickImage = image.id;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    callbackAndHide(imageResult) {
 | 
			
		||||
        if (callback) callback(imageResult);
 | 
			
		||||
        this.hide();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    saveImageDetails() {
 | 
			
		||||
        let url = window.baseUrl(`/images/update/${this.selectedImage.id}`);
 | 
			
		||||
        this.$http.put(url, this.selectedImage).then(response => {
 | 
			
		||||
            this.$events.emit('success', trans('components.image_update_success'));
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
            if (error.response.status === 422) {
 | 
			
		||||
                let errors = error.response.data;
 | 
			
		||||
                let message = '';
 | 
			
		||||
                Object.keys(errors).forEach((key) => {
 | 
			
		||||
                    message += errors[key].join('\n');
 | 
			
		||||
                });
 | 
			
		||||
                this.$events.emit('error', message);
 | 
			
		||||
            } else if (error.response.status === 403) {
 | 
			
		||||
                this.$events.emit('error', error.response.data.error);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    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 => {
 | 
			
		||||
            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;
 | 
			
		||||
            } else if (error.response.status === 403) {
 | 
			
		||||
                this.$events.emit('error', error.response.data.error);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getDate(stringDate) {
 | 
			
		||||
        return new Date(stringDate);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    uploadSuccess(event) {
 | 
			
		||||
        this.images.unshift(event.data);
 | 
			
		||||
        this.$events.emit('success', trans('components.image_upload_success'));
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const computed = {
 | 
			
		||||
    uploadUrl() {
 | 
			
		||||
        return window.baseUrl(`/images/${this.imageType}/upload`);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function mounted() {
 | 
			
		||||
    window.ImageManager = this;
 | 
			
		||||
    this.imageType = this.$el.getAttribute('image-type');
 | 
			
		||||
    this.uploadedTo = this.$el.getAttribute('uploaded-to');
 | 
			
		||||
    baseUrl = window.baseUrl('/images/' + this.imageType + '/all/')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    mounted,
 | 
			
		||||
    methods,
 | 
			
		||||
    data,
 | 
			
		||||
    computed,
 | 
			
		||||
    components: {dropzone},
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +149,7 @@ let methods = {
 | 
			
		|||
 | 
			
		||||
    updateSearch(e) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        window.location = '/search?term=' + encodeURIComponent(this.termString);
 | 
			
		||||
        window.location = window.baseUrl('/search?term=' + encodeURIComponent(this.termString));
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    enableDate(optionName) {
 | 
			
		||||
| 
						 | 
				
			
			@ -192,4 +192,4 @@ function created() {
 | 
			
		|||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    data, computed, methods, created
 | 
			
		||||
};
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,68 @@
 | 
			
		|||
const draggable = require('vuedraggable');
 | 
			
		||||
const autosuggest = require('./components/autosuggest');
 | 
			
		||||
 | 
			
		||||
let data = {
 | 
			
		||||
    pageId: false,
 | 
			
		||||
    tags: [],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const components = {draggable, autosuggest};
 | 
			
		||||
const directives = {};
 | 
			
		||||
 | 
			
		||||
let computed = {};
 | 
			
		||||
 | 
			
		||||
let methods = {
 | 
			
		||||
 | 
			
		||||
    addEmptyTag() {
 | 
			
		||||
        this.tags.push({name: '', value: '', key: Math.random().toString(36).substring(7)});
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When an tag changes check if another empty editable field needs to be added onto the end.
 | 
			
		||||
     * @param tag
 | 
			
		||||
     */
 | 
			
		||||
    tagChange(tag) {
 | 
			
		||||
        let tagPos = this.tags.indexOf(tag);
 | 
			
		||||
        if (tagPos === this.tags.length-1 && (tag.name !== '' || tag.value !== '')) this.addEmptyTag();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When an tag field loses focus check the tag to see if its
 | 
			
		||||
     * empty and therefore could be removed from the list.
 | 
			
		||||
     * @param tag
 | 
			
		||||
     */
 | 
			
		||||
    tagBlur(tag) {
 | 
			
		||||
        let isLast = (this.tags.indexOf(tag) === this.tags.length-1);
 | 
			
		||||
        if (tag.name !== '' || tag.value !== '' || isLast) return;
 | 
			
		||||
        let cPos = this.tags.indexOf(tag);
 | 
			
		||||
        this.tags.splice(cPos, 1);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    removeTag(tag) {
 | 
			
		||||
        let tagPos = this.tags.indexOf(tag);
 | 
			
		||||
        if (tagPos === -1) return;
 | 
			
		||||
        this.tags.splice(tagPos, 1);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getTagFieldName(index, key) {
 | 
			
		||||
        return `tags[${index}][${key}]`;
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function mounted() {
 | 
			
		||||
    this.pageId = Number(this.$el.getAttribute('page-id'));
 | 
			
		||||
 | 
			
		||||
    let url = window.baseUrl(`/ajax/tags/get/page/${this.pageId}`);
 | 
			
		||||
    this.$http.get(url).then(response => {
 | 
			
		||||
        let tags = response.data;
 | 
			
		||||
        for (let i = 0, len = tags.length; i < len; i++) {
 | 
			
		||||
            tags[i].key = Math.random().toString(36).substring(7);
 | 
			
		||||
        }
 | 
			
		||||
        this.tags = tags;
 | 
			
		||||
        this.addEmptyTag();
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    data, computed, methods, mounted, components, directives
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -7,12 +7,17 @@ function exists(id) {
 | 
			
		|||
let vueMapping = {
 | 
			
		||||
    'search-system': require('./search'),
 | 
			
		||||
    'entity-dashboard': require('./entity-search'),
 | 
			
		||||
    'code-editor': require('./code-editor'),
 | 
			
		||||
    'image-manager': require('./image-manager'),
 | 
			
		||||
    'tag-manager': require('./tag-manager'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
window.vues = {};
 | 
			
		||||
 | 
			
		||||
Object.keys(vueMapping).forEach(id => {
 | 
			
		||||
    if (exists(id)) {
 | 
			
		||||
        let config = vueMapping[id];
 | 
			
		||||
        config.el = '#' + id;
 | 
			
		||||
        new Vue(config);
 | 
			
		||||
        window.vues[id] = new Vue(config);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -36,41 +36,12 @@
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.anim.notification {
 | 
			
		||||
  transform: translate3d(580px, 0, 0);
 | 
			
		||||
  animation-name: notification;
 | 
			
		||||
  animation-duration: 3s;
 | 
			
		||||
  animation-timing-function: ease-in-out;
 | 
			
		||||
  animation-fill-mode: forwards;
 | 
			
		||||
  &.stopped {
 | 
			
		||||
    animation-name: notificationStopped;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes notification {
 | 
			
		||||
  0% {
 | 
			
		||||
    transform: translate3d(580px, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
  10% {
 | 
			
		||||
    transform: translate3d(0, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
  90% {
 | 
			
		||||
    transform: translate3d(0, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
  100% {
 | 
			
		||||
    transform: translate3d(580px, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@keyframes notificationStopped {
 | 
			
		||||
  0% {
 | 
			
		||||
    transform: translate3d(580px, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
  10% {
 | 
			
		||||
    transform: translate3d(0, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
  100% {
 | 
			
		||||
    transform: translate3d(0, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
.anim.menuIn {
 | 
			
		||||
  transform-origin: 100% 0%;
 | 
			
		||||
  animation-name: menuIn;
 | 
			
		||||
  animation-duration: 120ms;
 | 
			
		||||
  animation-delay: 0s;
 | 
			
		||||
  animation-timing-function: cubic-bezier(.62, .28, .23, .99);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes menuIn {
 | 
			
		||||
| 
						 | 
				
			
			@ -85,14 +56,6 @@
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.anim.menuIn {
 | 
			
		||||
  transform-origin: 100% 0%;
 | 
			
		||||
  animation-name: menuIn;
 | 
			
		||||
  animation-duration: 120ms;
 | 
			
		||||
  animation-delay: 0s;
 | 
			
		||||
  animation-timing-function: cubic-bezier(.62, .28, .23, .99);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes loadingBob {
 | 
			
		||||
  0% {
 | 
			
		||||
    transform: translate3d(0, 0, 0);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -248,6 +248,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
 | 
			
		|||
  -webkit-tap-highlight-color: transparent;
 | 
			
		||||
  -webkit-font-variant-ligatures: contextual;
 | 
			
		||||
  font-variant-ligatures: contextual;
 | 
			
		||||
  &:after {
 | 
			
		||||
    content: none;
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.CodeMirror-wrap pre {
 | 
			
		||||
  word-wrap: break-word;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,65 @@
 | 
			
		|||
.overlay {
 | 
			
		||||
// System wide notifications
 | 
			
		||||
[notification] {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  margin: $-xl*2 $-xl;
 | 
			
		||||
  padding: $-l $-xl;
 | 
			
		||||
  background-color: #EEE;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  box-shadow: $bs-med;
 | 
			
		||||
  z-index: 999999;
 | 
			
		||||
  display: block;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  max-width: 480px;
 | 
			
		||||
  transition: transform ease-in-out 360ms;
 | 
			
		||||
  transform: translate3d(580px, 0, 0);
 | 
			
		||||
  i, span {
 | 
			
		||||
    display: table-cell;
 | 
			
		||||
  }
 | 
			
		||||
  i {
 | 
			
		||||
    font-size: 2em;
 | 
			
		||||
    padding-right: $-l;
 | 
			
		||||
  }
 | 
			
		||||
  span {
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
  }
 | 
			
		||||
  &.pos {
 | 
			
		||||
    background-color: $positive;
 | 
			
		||||
    color: #EEE;
 | 
			
		||||
  }
 | 
			
		||||
  &.neg {
 | 
			
		||||
    background-color: $negative;
 | 
			
		||||
    color: #EEE;
 | 
			
		||||
  }
 | 
			
		||||
  &.warning {
 | 
			
		||||
    background-color: $secondary;
 | 
			
		||||
    color: #EEE;
 | 
			
		||||
  }
 | 
			
		||||
  &.showing {
 | 
			
		||||
    transform: translate3d(0, 0, 0);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[chapter-toggle] {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  transition: all ease-in-out 180ms;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
  i.zmdi-caret-right {
 | 
			
		||||
    transition: all ease-in-out 180ms;
 | 
			
		||||
    transform: rotate(0deg);
 | 
			
		||||
    transform-origin: 25% 50%;
 | 
			
		||||
  }
 | 
			
		||||
  &.open {
 | 
			
		||||
    //margin-bottom: 0;
 | 
			
		||||
  }
 | 
			
		||||
  &.open i.zmdi-caret-right {
 | 
			
		||||
    transform: rotate(90deg);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[overlay] {
 | 
			
		||||
  background-color: rgba(0, 0, 0, 0.333);
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  z-index: 95536;
 | 
			
		||||
| 
						 | 
				
			
			@ -466,4 +527,17 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 | 
			
		|||
 | 
			
		||||
.image-picker .none {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#code-editor .CodeMirror {
 | 
			
		||||
  height: 400px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#code-editor .lang-options {
 | 
			
		||||
  max-width: 400px;
 | 
			
		||||
  margin-bottom: $-s;
 | 
			
		||||
  a {
 | 
			
		||||
    margin-right: $-xs;
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +32,7 @@
 | 
			
		|||
#markdown-editor {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  z-index: 5;
 | 
			
		||||
  textarea {
 | 
			
		||||
  #markdown-editor-input {
 | 
			
		||||
    font-family: 'Roboto Mono', monospace;
 | 
			
		||||
    font-style: normal;
 | 
			
		||||
    font-weight: 400;
 | 
			
		||||
| 
						 | 
				
			
			@ -265,7 +265,7 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input.outline {
 | 
			
		||||
.outline > input {
 | 
			
		||||
  border: 0;
 | 
			
		||||
  border-bottom: 2px solid #DDD;
 | 
			
		||||
  border-radius: 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -142,7 +142,6 @@ form.search-box {
 | 
			
		|||
  color: #aaa;
 | 
			
		||||
  padding: 0 $-xs;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.faded {
 | 
			
		||||
  a, button, span, span > div {
 | 
			
		||||
    color: #666;
 | 
			
		||||
| 
						 | 
				
			
			@ -155,7 +154,6 @@ form.search-box {
 | 
			
		|||
      text-decoration: none;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.faded span.faded-text {
 | 
			
		||||
| 
						 | 
				
			
			@ -175,6 +173,15 @@ form.search-box {
 | 
			
		|||
  &:last-child {
 | 
			
		||||
    padding-right: 0;
 | 
			
		||||
  }
 | 
			
		||||
  &:first-child {
 | 
			
		||||
    padding-left: 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.action-buttons .dropdown-container:last-child a {
 | 
			
		||||
  padding-right: 0;
 | 
			
		||||
  padding-left: $-s;
 | 
			
		||||
}
 | 
			
		||||
.action-buttons {
 | 
			
		||||
  text-align: right;
 | 
			
		||||
| 
						 | 
				
			
			@ -190,6 +197,25 @@ form.search-box {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@include smaller-than($m) {
 | 
			
		||||
  .breadcrumbs .text-button, .action-buttons .text-button {
 | 
			
		||||
    padding: $-s $-xs;
 | 
			
		||||
  }
 | 
			
		||||
  .action-buttons .dropdown-container:last-child a {
 | 
			
		||||
    padding-left: $-xs;
 | 
			
		||||
  }
 | 
			
		||||
  .breadcrumbs .text-button {
 | 
			
		||||
    font-size: 0;
 | 
			
		||||
  }
 | 
			
		||||
  .breadcrumbs a i {
 | 
			
		||||
    font-size: $fs-m;
 | 
			
		||||
    padding-right: 0;
 | 
			
		||||
  }
 | 
			
		||||
  .breadcrumbs span.sep {
 | 
			
		||||
    padding: 0 $-xxs;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-tabs {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  a, .tab-item {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,6 @@
 | 
			
		|||
  .inset-list {
 | 
			
		||||
    display: none;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin-bottom: $-l;
 | 
			
		||||
  }
 | 
			
		||||
  h5 {
 | 
			
		||||
    display: block;
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +21,9 @@
 | 
			
		|||
      border-left-color: $color-page-draft;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .entity-list-item {
 | 
			
		||||
    margin-bottom: $-m;
 | 
			
		||||
  }
 | 
			
		||||
  hr {
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -51,23 +53,6 @@
 | 
			
		|||
    margin-right: $-s;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.chapter-toggle {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  margin: 0 0 $-l 0;
 | 
			
		||||
  transition: all ease-in-out 180ms;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
  i.zmdi-caret-right {
 | 
			
		||||
    transition: all ease-in-out 180ms;
 | 
			
		||||
    transform: rotate(0deg);
 | 
			
		||||
    transform-origin: 25% 50%;
 | 
			
		||||
  }
 | 
			
		||||
  &.open {
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
  }
 | 
			
		||||
  &.open i.zmdi-caret-right {
 | 
			
		||||
    transform: rotate(90deg);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebar-page-nav {
 | 
			
		||||
  $nav-indent: $-s;
 | 
			
		||||
| 
						 | 
				
			
			@ -171,7 +156,7 @@
 | 
			
		|||
      background-color: rgba($color-chapter, 0.12);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .chapter-toggle {
 | 
			
		||||
  [chapter-toggle] {
 | 
			
		||||
    padding-left: $-s;
 | 
			
		||||
  }
 | 
			
		||||
  .list-item-chapter {
 | 
			
		||||
| 
						 | 
				
			
			@ -336,8 +321,10 @@ ul.pagination {
 | 
			
		|||
  h4, a {
 | 
			
		||||
    line-height: 1.2;
 | 
			
		||||
  }
 | 
			
		||||
  p {
 | 
			
		||||
  .entity-item-snippet {
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
  p {
 | 
			
		||||
    font-size: $fs-m * 0.8;
 | 
			
		||||
    padding-top: $-xs;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -226,7 +226,7 @@
 | 
			
		|||
    width: 100%;
 | 
			
		||||
    min-width: 50px;
 | 
			
		||||
  }
 | 
			
		||||
  .tags td {
 | 
			
		||||
  .tags td, .tag-table > div > div > div {
 | 
			
		||||
    padding-right: $-s;
 | 
			
		||||
    padding-top: $-s;
 | 
			
		||||
    position: relative;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,4 +67,17 @@ table.file-table {
 | 
			
		|||
  .ui-sortable-helper {
 | 
			
		||||
    display: table;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fake-table {
 | 
			
		||||
  display: table;
 | 
			
		||||
  > div {
 | 
			
		||||
    display: table-row-group;
 | 
			
		||||
  }
 | 
			
		||||
  > div > div {
 | 
			
		||||
    display: table-row;
 | 
			
		||||
  }
 | 
			
		||||
  > div > div > div {
 | 
			
		||||
    display: table-cell;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -135,8 +135,31 @@ pre {
 | 
			
		|||
  font-size: 12px;
 | 
			
		||||
  background-color: #f5f5f5;
 | 
			
		||||
  border: 1px solid #DDD;
 | 
			
		||||
  padding-left: 31px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  padding-top: 3px;
 | 
			
		||||
  padding-bottom: 3px;
 | 
			
		||||
  &:after {
 | 
			
		||||
    content: '';
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    width: 29px;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    background-color: #f5f5f5;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    border-right: 1px solid #DDD;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media print {
 | 
			
		||||
  pre {
 | 
			
		||||
    padding-left: 12px;
 | 
			
		||||
  }
 | 
			
		||||
  pre:after {
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
blockquote {
 | 
			
		||||
  display: block;
 | 
			
		||||
| 
						 | 
				
			
			@ -182,6 +205,7 @@ pre code {
 | 
			
		|||
  border: 0;
 | 
			
		||||
  font-size: 1em;
 | 
			
		||||
  display: block;
 | 
			
		||||
  line-height: 1.6;
 | 
			
		||||
}
 | 
			
		||||
/*
 | 
			
		||||
 * Text colors
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,44 +66,6 @@ body.dragging, body.dragging * {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// System wide notifications
 | 
			
		||||
.notification {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  margin: $-xl*2 $-xl;
 | 
			
		||||
  padding: $-l $-xl;
 | 
			
		||||
  background-color: #EEE;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  box-shadow: $bs-med;
 | 
			
		||||
  z-index: 999999;
 | 
			
		||||
  display: block;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  max-width: 480px;
 | 
			
		||||
  i, span {
 | 
			
		||||
    display: table-cell;
 | 
			
		||||
  }
 | 
			
		||||
  i {
 | 
			
		||||
    font-size: 2em;
 | 
			
		||||
    padding-right: $-l;
 | 
			
		||||
  }
 | 
			
		||||
  span {
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
  }
 | 
			
		||||
  &.pos {
 | 
			
		||||
    background-color: $positive;
 | 
			
		||||
    color: #EEE;
 | 
			
		||||
  }
 | 
			
		||||
  &.neg {
 | 
			
		||||
    background-color: $negative;
 | 
			
		||||
    color: #EEE;
 | 
			
		||||
  }
 | 
			
		||||
  &.warning {
 | 
			
		||||
    background-color: $secondary;
 | 
			
		||||
    color: #EEE;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Loading icon
 | 
			
		||||
$loadingSize: 10px;
 | 
			
		||||
.loading-container {
 | 
			
		||||
| 
						 | 
				
			
			@ -151,7 +113,7 @@ $loadingSize: 10px;
 | 
			
		|||
 | 
			
		||||
// Back to top link
 | 
			
		||||
$btt-size: 40px;
 | 
			
		||||
#back-to-top {
 | 
			
		||||
[back-to-top] {
 | 
			
		||||
  background-color: $primary;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  bottom: $-m;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,5 +20,13 @@ return [
 | 
			
		|||
    'image_preview' => 'Image Preview',
 | 
			
		||||
    'image_upload_success' => 'Image uploaded successfully',
 | 
			
		||||
    'image_update_success' => 'Image details successfully updated',
 | 
			
		||||
    'image_delete_success' => 'Image successfully deleted'
 | 
			
		||||
    'image_delete_success' => 'Image successfully deleted',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Code editor
 | 
			
		||||
     */
 | 
			
		||||
    'code_editor' => 'Edit Code',
 | 
			
		||||
    'code_language' => 'Code Language',
 | 
			
		||||
    'code_content' => 'Code Content',
 | 
			
		||||
    'code_save' => 'Save Code',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -121,6 +121,8 @@ return [
 | 
			
		|||
        'nl' => 'Nederlands',
 | 
			
		||||
        'pt_BR' => 'Português do Brasil',
 | 
			
		||||
        'sk' => 'Slovensky',
 | 
			
		||||
        'ja' => '日本語',
 | 
			
		||||
        'pl' => 'Polski',
 | 
			
		||||
    ]
 | 
			
		||||
    ///////////////////////////////////
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@ return [
 | 
			
		|||
    | these language lines according to your application's requirements.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
    'failed' => 'Ces informations ne correspondent a aucun compte.',
 | 
			
		||||
    'failed' => 'Ces informations ne correspondent à aucun compte.',
 | 
			
		||||
    'throttle' => "Trop d'essais, veuillez réessayer dans :seconds secondes.",
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ return [
 | 
			
		|||
    'password' => 'Mot de passe',
 | 
			
		||||
    'password_confirm' => 'Confirmez le mot de passe',
 | 
			
		||||
    'password_hint' => 'Doit faire plus de 5 caractères',
 | 
			
		||||
    'forgot_password' => 'Mot de passe oublié?',
 | 
			
		||||
    'forgot_password' => 'Mot de passe oublié ?',
 | 
			
		||||
    'remember_me' => 'Se souvenir de moi',
 | 
			
		||||
    'ldap_email_hint' => "Merci d'entrer une adresse e-mail pour ce compte",
 | 
			
		||||
    'create_account' => 'Créer un compte',
 | 
			
		||||
| 
						 | 
				
			
			@ -35,9 +35,9 @@ return [
 | 
			
		|||
    'social_registration_text' => "S'inscrire et se connecter avec un réseau social",
 | 
			
		||||
 | 
			
		||||
    'register_thanks' => 'Merci pour votre enregistrement',
 | 
			
		||||
    'register_confirm' => 'Vérifiez vos e-mails et cliquer sur le lien de confirmation pour rejoindre :appName.',
 | 
			
		||||
    'register_confirm' => 'Vérifiez vos e-mails et cliquez sur le lien de confirmation pour rejoindre :appName.',
 | 
			
		||||
    'registrations_disabled' => "L'inscription est désactivée pour le moment",
 | 
			
		||||
    'registration_email_domain_invalid' => 'Cette adresse e-mail ne peux pas adcéder à l\'application',
 | 
			
		||||
    'registration_email_domain_invalid' => 'Cette adresse e-mail ne peut pas accéder à l\'application',
 | 
			
		||||
    'register_success' => 'Merci pour votre inscription. Vous êtes maintenant inscrit(e) et connecté(e)',
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ return [
 | 
			
		|||
    'reset_password_success' => 'Votre mot de passe a été réinitialisé avec succès.',
 | 
			
		||||
 | 
			
		||||
    'email_reset_subject' => 'Réinitialisez votre mot de passe pour :appName',
 | 
			
		||||
    'email_reset_text' => 'Vous recevez cet e-mail parceque nous avons reçu une demande de réinitialisation pour votre compte',
 | 
			
		||||
    'email_reset_text' => 'Vous recevez cet e-mail parce que nous avons reçu une demande de réinitialisation pour votre compte',
 | 
			
		||||
    'email_reset_not_requested' => 'Si vous n\'avez pas effectué cette demande, vous pouvez ignorer cet e-mail.',
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -59,11 +59,11 @@ return [
 | 
			
		|||
     * Email Confirmation
 | 
			
		||||
     */
 | 
			
		||||
    'email_confirm_subject' => 'Confirmez votre adresse e-mail pour :appName',
 | 
			
		||||
    'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName!',
 | 
			
		||||
    'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous:',
 | 
			
		||||
    'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName !',
 | 
			
		||||
    'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous :',
 | 
			
		||||
    'email_confirm_action' => 'Confirmez votre adresse e-mail',
 | 
			
		||||
    'email_confirm_send_error' => 'La confirmation par e-mail est requise mais le système n\'a pas pu envoyer l\'e-mail. Contactez l\'administrateur système.',
 | 
			
		||||
    'email_confirm_success' => 'Votre adresse e-mail a été confirmée!',
 | 
			
		||||
    'email_confirm_success' => 'Votre adresse e-mail a été confirmée !',
 | 
			
		||||
    'email_confirm_resent' => 'L\'e-mail de confirmation a été ré-envoyé. Vérifiez votre boîte de récéption.',
 | 
			
		||||
 | 
			
		||||
    'email_not_confirmed' => 'Adresse e-mail non confirmée',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ return [
 | 
			
		|||
    'back' => 'Retour',
 | 
			
		||||
    'save' => 'Enregistrer',
 | 
			
		||||
    'continue' => 'Continuer',
 | 
			
		||||
    'select' => 'Selectionner',
 | 
			
		||||
    'select' => 'Sélectionner',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Form Labels
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +53,6 @@ return [
 | 
			
		|||
    /**
 | 
			
		||||
     * Email Content
 | 
			
		||||
     */
 | 
			
		||||
    'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur:',
 | 
			
		||||
    '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',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ return [
 | 
			
		|||
    'recently_update' => 'Mis à jour récemment',
 | 
			
		||||
    'recently_viewed' => 'Vus récemment',
 | 
			
		||||
    'recent_activity' => 'Activité récente',
 | 
			
		||||
    'create_now' => 'En créer un récemment',
 | 
			
		||||
    'create_now' => 'En créer un maintenant',
 | 
			
		||||
    'revisions' => 'Révisions',
 | 
			
		||||
    'meta_created' => 'Créé :timeLength',
 | 
			
		||||
    'meta_created_name' => 'Créé :timeLength par :user',
 | 
			
		||||
| 
						 | 
				
			
			@ -59,8 +59,8 @@ return [
 | 
			
		|||
    'books_create' => 'Créer un nouveau livre',
 | 
			
		||||
    'books_delete' => 'Supprimer un livre',
 | 
			
		||||
    'books_delete_named' => 'Supprimer le livre :bookName',
 | 
			
		||||
    'books_delete_explain' => 'Ceci va supprimer le livre nommé \':bookName\', Tous les chapitres et pages seront supprimés.',
 | 
			
		||||
    'books_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer ce livre?',
 | 
			
		||||
    'books_delete_explain' => 'Ceci va supprimer le livre nommé \':bookName\', tous les chapitres et pages seront supprimés.',
 | 
			
		||||
    'books_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer ce livre ?',
 | 
			
		||||
    'books_edit' => 'Modifier le livre',
 | 
			
		||||
    'books_edit_named' => 'Modifier le livre :bookName',
 | 
			
		||||
    'books_form_book_name' => 'Nom du livre',
 | 
			
		||||
| 
						 | 
				
			
			@ -90,18 +90,18 @@ return [
 | 
			
		|||
    'chapters_create' => 'Créer un nouveau chapitre',
 | 
			
		||||
    'chapters_delete' => 'Supprimer le chapitre',
 | 
			
		||||
    'chapters_delete_named' => 'Supprimer le chapitre :chapterName',
 | 
			
		||||
    'chapters_delete_explain' => 'Ceci va supprimer le chapitre \':chapterName\', Toutes les pages seront déplacée dans le livre parent.',
 | 
			
		||||
    'chapters_delete_confirm' => 'Etes-vous sûr(e) de vouloir supprimer ce chapitre?',
 | 
			
		||||
    'chapters_delete_explain' => 'Ceci va supprimer le chapitre \':chapterName\', toutes les pages seront déplacées dans le livre parent.',
 | 
			
		||||
    'chapters_delete_confirm' => 'Etes-vous sûr(e) de vouloir supprimer ce chapitre ?',
 | 
			
		||||
    'chapters_edit' => 'Modifier le chapitre',
 | 
			
		||||
    'chapters_edit_named' => 'Modifier le chapitre :chapterName',
 | 
			
		||||
    'chapters_save' => 'Enregistrer le chapitre',
 | 
			
		||||
    'chapters_move' => 'Déplace le chapitre',
 | 
			
		||||
    'chapters_move' => 'Déplacer le chapitre',
 | 
			
		||||
    'chapters_move_named' => 'Déplacer le chapitre :chapterName',
 | 
			
		||||
    'chapter_move_success' => 'Chapitre déplacé dans :bookName',
 | 
			
		||||
    'chapters_permissions' => 'Permissions du chapitre',
 | 
			
		||||
    'chapters_empty' => 'Il n\'y a pas de pages dans ce chapitre actuellement.',
 | 
			
		||||
    'chapters_empty' => 'Il n\'y a pas de page dans ce chapitre actuellement.',
 | 
			
		||||
    'chapters_permissions_active' => 'Permissions du chapitre activées',
 | 
			
		||||
    'chapters_permissions_success' => 'Permissions du chapitres mises à jour',
 | 
			
		||||
    'chapters_permissions_success' => 'Permissions du chapitre mises à jour',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Pages
 | 
			
		||||
| 
						 | 
				
			
			@ -118,8 +118,8 @@ return [
 | 
			
		|||
    'pages_delete_draft' => 'Supprimer le brouillon',
 | 
			
		||||
    'pages_delete_success' => 'Page supprimée',
 | 
			
		||||
    'pages_delete_draft_success' => 'Brouillon supprimé',
 | 
			
		||||
    'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page?',
 | 
			
		||||
    'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon?',
 | 
			
		||||
    'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page ?',
 | 
			
		||||
    'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon ?',
 | 
			
		||||
    'pages_editing_named' => 'Modification de la page :pageName',
 | 
			
		||||
    'pages_edit_toggle_header' => 'Afficher/cacher l\'en-tête',
 | 
			
		||||
    'pages_edit_save_draft' => 'Enregistrer le brouillon',
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +131,7 @@ return [
 | 
			
		|||
    'pages_edit_discard_draft' => 'Ecarter le brouillon',
 | 
			
		||||
    'pages_edit_set_changelog' => 'Remplir le journal des changements',
 | 
			
		||||
    'pages_edit_enter_changelog_desc' => 'Entrez une brève description des changements effectués',
 | 
			
		||||
    'pages_edit_enter_changelog' => 'Entrez dans le journal des changements',
 | 
			
		||||
    'pages_edit_enter_changelog' => 'Entrer dans le journal des changements',
 | 
			
		||||
    'pages_save' => 'Enregistrez la page',
 | 
			
		||||
    'pages_title' => 'Titre de la page',
 | 
			
		||||
    'pages_name' => 'Nom de la page',
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +139,7 @@ return [
 | 
			
		|||
    'pages_md_preview' => 'Prévisualisation',
 | 
			
		||||
    'pages_md_insert_image' => 'Insérer une image',
 | 
			
		||||
    'pages_md_insert_link' => 'Insérer un lien',
 | 
			
		||||
    'pages_not_in_chapter' => 'La page n\'est pas dans un chanpitre',
 | 
			
		||||
    '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_permissions' => 'Permissions de la page',
 | 
			
		||||
| 
						 | 
				
			
			@ -160,15 +160,15 @@ return [
 | 
			
		|||
    'pages_initial_revision' => 'Publication initiale',
 | 
			
		||||
    'pages_initial_name' => 'Nouvelle page',
 | 
			
		||||
    'pages_editing_draft_notification' => 'Vous éditez actuellement un brouillon qui a été sauvé :timeDiff.',
 | 
			
		||||
    'pages_draft_edited_notification' => 'La page a été mise à jour depuis votre dernière visit. Vous devriez écarter ce brouillon.',
 | 
			
		||||
    'pages_draft_edited_notification' => 'La page a été mise à jour depuis votre dernière visite. Vous devriez écarter ce brouillon.',
 | 
			
		||||
    'pages_draft_edit_active' => [
 | 
			
		||||
        'start_a' => ':count utilisateurs ont commencé a éditer cette page',
 | 
			
		||||
        'start_a' => ':count utilisateurs ont commencé à éditer cette page',
 | 
			
		||||
        'start_b' => ':userName a commencé à éditer cette page',
 | 
			
		||||
        'time_a' => 'depuis la dernière sauvegarde',
 | 
			
		||||
        'time_b' => 'dans les :minCount dernières minutes',
 | 
			
		||||
        'message' => ':start :time. Attention a ne pas écraser les mises à jour de quelqu\'un d\'autre!',
 | 
			
		||||
        'message' => ':start :time. Attention à ne pas écraser les mises à jour de quelqu\'un d\'autre !',
 | 
			
		||||
    ],
 | 
			
		||||
    'pages_draft_discarded' => 'Brouuillon écarté, la page est dans sa version actuelle.',
 | 
			
		||||
    'pages_draft_discarded' => 'Brouillon écarté, la page est dans sa version actuelle.',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Editor sidebar
 | 
			
		||||
| 
						 | 
				
			
			@ -210,9 +210,9 @@ return [
 | 
			
		|||
     */
 | 
			
		||||
    'profile_user_for_x' => 'Utilisateur depuis :time',
 | 
			
		||||
    'profile_created_content' => 'Contenu créé',
 | 
			
		||||
    'profile_not_created_pages' => ':userName n\'a pas créé de pages',
 | 
			
		||||
    'profile_not_created_chapters' => ':userName n\'a pas créé de chapitres',
 | 
			
		||||
    'profile_not_created_books' => ':userName n\'a pas créé de livres',
 | 
			
		||||
    'profile_not_created_pages' => ':userName n\'a pas créé de page',
 | 
			
		||||
    'profile_not_created_chapters' => ':userName n\'a pas créé de chapitre',
 | 
			
		||||
    'profile_not_created_books' => ':userName n\'a pas créé de livre',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Comments
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,21 +18,21 @@ return [
 | 
			
		|||
    '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_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed',
 | 
			
		||||
    'social_no_action_defined' => 'No action defined',
 | 
			
		||||
    'social_account_in_use' => 'Cet 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.',
 | 
			
		||||
    '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_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.',
 | 
			
		||||
    'social_account_already_used_existing' => 'Ce compte :socialAccount est déjà utilisé par un autre utilisateur.',
 | 
			
		||||
    'social_account_not_used' => 'Ce compte :socialAccount n\'est lié à aucun utilisateur. ',
 | 
			
		||||
    'social_account_register_instructions' => 'Si vous n\'avez pas encore de compte, vous pouvez le lier avec l\'option :socialAccount.',
 | 
			
		||||
    'social_driver_not_found' => 'Social driver not found',
 | 
			
		||||
    'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
 | 
			
		||||
    'social_driver_not_found' => 'Pilote de compte social absent',
 | 
			
		||||
    'social_driver_not_configured' => 'Vos préférences pour le compte :socialAccount sont incorrectes.',
 | 
			
		||||
 | 
			
		||||
    // System
 | 
			
		||||
    'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.',
 | 
			
		||||
    'path_not_writable' => 'Impossible d\'écrire dans :filePath. Assurez-vous d\'avoir les droits d\'écriture sur le serveur',
 | 
			
		||||
    'cannot_get_image_from_url' => 'Impossible de récupérer l\'image depuis :url',
 | 
			
		||||
    'cannot_create_thumbs' => 'Le serveur ne peux pas créer de miniatures, vérifier que l\extensions GD PHP est installée.',
 | 
			
		||||
    '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.',
 | 
			
		||||
    'image_upload_error' => 'Une erreur est survenue pendant l\'envoi de l\'image',
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +57,7 @@ return [
 | 
			
		|||
 | 
			
		||||
    // Roles
 | 
			
		||||
    'role_cannot_be_edited' => 'Ce rôle ne peut pas être modifié',
 | 
			
		||||
    'role_system_cannot_be_deleted' => 'Ceci est un rôle du système et on ne peut pas le supprimer',
 | 
			
		||||
    'role_system_cannot_be_deleted' => 'Ceci est un rôle du système et ne peut pas être supprimé',
 | 
			
		||||
    'role_registration_default_cannot_delete' => 'Ce rôle ne peut pas être supprimé tant qu\'il est le rôle par défaut',
 | 
			
		||||
 | 
			
		||||
    // Error pages
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@ return [
 | 
			
		|||
    'password' => 'Les mots de passe doivent faire au moins 6 caractères et correspondre à la confirmation.',
 | 
			
		||||
    'user' => "Nous n'avons pas trouvé d'utilisateur avec cette adresse.",
 | 
			
		||||
    'token' => 'Le jeton de réinitialisation est invalide.',
 | 
			
		||||
    'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe!',
 | 
			
		||||
    'reset' => 'Votre mot de passe a été réinitialisé!',
 | 
			
		||||
    'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe !',
 | 
			
		||||
    'reset' => 'Votre mot de passe a été réinitialisé !',
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,27 +19,27 @@ return [
 | 
			
		|||
    'app_settings' => 'Préférences de l\'application',
 | 
			
		||||
    'app_name' => 'Nom de l\'application',
 | 
			
		||||
    'app_name_desc' => 'Ce nom est affiché dans l\'en-tête et les e-mails.',
 | 
			
		||||
    'app_name_header' => 'Afficher le nom dans l\'en-tête?',
 | 
			
		||||
    'app_public_viewing' => 'Accepter le visionnage public des pages?',
 | 
			
		||||
    'app_secure_images' => 'Activer l\'ajout d\'image sécurisé?',
 | 
			
		||||
    'app_name_header' => 'Afficher le nom dans l\'en-tête ?',
 | 
			
		||||
    'app_public_viewing' => 'Accepter le visionnage public des pages ?',
 | 
			
		||||
    'app_secure_images' => 'Activer l\'ajout d\'image sécurisé ?',
 | 
			
		||||
    'app_secure_images_desc' => 'Pour des questions de performances, toutes les images sont publiques. Cette option ajoute une chaîne aléatoire difficile à deviner dans les URLs des images.',
 | 
			
		||||
    'app_editor' => 'Editeur des pages',
 | 
			
		||||
    'app_editor_desc' => 'Sélectionnez l\'éditeur qui sera utilisé pour modifier les pages.',
 | 
			
		||||
    'app_custom_html' => 'HTML personnalisé dans l\'en-tête',
 | 
			
		||||
    'app_custom_html_desc' => 'Le contenu inséré ici sera jouté en bas de la balise <head> de toutes les pages. Vous pouvez l\'utiliser pour ajouter du CSS personnalisé ou un tracker analytique.',
 | 
			
		||||
    'app_custom_html_desc' => 'Le contenu inséré ici sera ajouté en bas de la balise <head> de toutes les pages. Vous pouvez l\'utiliser pour ajouter du CSS personnalisé ou un tracker analytique.',
 | 
			
		||||
    'app_logo' => 'Logo de l\'Application',
 | 
			
		||||
    'app_logo_desc' => 'Cette image doit faire 43px de hauteur. <br>Les images plus larges seront réduites.',
 | 
			
		||||
    'app_primary_color' => 'Couleur principale de l\'application',
 | 
			
		||||
    'app_primary_color_desc' => 'This should be a hex value. <br>Leave empty to reset to the default color.',
 | 
			
		||||
    'app_primary_color_desc' => 'Cela devrait être une valeur hexadécimale. <br>Laisser vide pour rétablir la couleur par défaut.',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registration settings
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'reg_settings' => 'Préférence pour l\'inscription',
 | 
			
		||||
    'reg_allow' => 'Accepter l\'inscription?',
 | 
			
		||||
    'reg_allow' => 'Accepter l\'inscription ?',
 | 
			
		||||
    'reg_default_role' => 'Rôle par défaut lors de l\'inscription',
 | 
			
		||||
    'reg_confirm_email' => 'Obliger la confirmation par e-mail?',
 | 
			
		||||
    '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.',
 | 
			
		||||
| 
						 | 
				
			
			@ -57,17 +57,17 @@ 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(e) 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_name' => 'Nom du rôle',
 | 
			
		||||
    'role_desc' => 'Courte description du rôle',
 | 
			
		||||
    '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_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.',
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +94,7 @@ return [
 | 
			
		|||
    'users_delete' => 'Supprimer un utilisateur',
 | 
			
		||||
    'users_delete_named' => 'Supprimer l\'utilisateur :userName',
 | 
			
		||||
    'users_delete_warning' => 'Ceci va supprimer \':userName\' du système.',
 | 
			
		||||
    'users_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cet utilisateur?',
 | 
			
		||||
    'users_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cet utilisateur ?',
 | 
			
		||||
    'users_delete_success' => 'Utilisateurs supprimés avec succès',
 | 
			
		||||
    'users_edit' => 'Modifier l\'utilisateur',
 | 
			
		||||
    'users_edit_profile' => 'Modifier le profil',
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +106,7 @@ return [
 | 
			
		|||
    'users_social_accounts_info' => 'Vous pouvez connecter des réseaux sociaux à votre compte pour vous connecter plus rapidement. Déconnecter un compte n\'enlèvera pas les accès autorisés précédemment sur votre compte de réseau social.',
 | 
			
		||||
    'users_social_connect' => 'Connecter le compte',
 | 
			
		||||
    'users_social_disconnect' => 'Déconnecter le compte',
 | 
			
		||||
    'users_social_connected' => 'Votre compte :socialAccount a élté ajouté avec succès.',
 | 
			
		||||
    'users_social_connected' => 'Votre compte :socialAccount a été ajouté avec succès.',
 | 
			
		||||
    'users_social_disconnected' => 'Votre compte :socialAccount a été déconnecté avec succès',
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Activity text strings.
 | 
			
		||||
     * Is used for all the text within activity logs & notifications.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    // Pages
 | 
			
		||||
    'page_create'                 => 'がページを作成:',
 | 
			
		||||
    'page_create_notification'    => 'ページを作成しました',
 | 
			
		||||
    'page_update'                 => 'がページを更新:',
 | 
			
		||||
    'page_update_notification'    => 'ページを更新しました',
 | 
			
		||||
    'page_delete'                 => 'がページを削除:',
 | 
			
		||||
    'page_delete_notification'    => 'ページを削除しました',
 | 
			
		||||
    'page_restore'                => 'がページを復元:',
 | 
			
		||||
    'page_restore_notification'   => 'ページを復元しました',
 | 
			
		||||
    'page_move'                   => 'がページを移動:',
 | 
			
		||||
 | 
			
		||||
    // Chapters
 | 
			
		||||
    'chapter_create'              => 'がチャプターを作成:',
 | 
			
		||||
    'chapter_create_notification' => 'チャプターを作成しました',
 | 
			
		||||
    'chapter_update'              => 'がチャプターを更新:',
 | 
			
		||||
    'chapter_update_notification' => 'チャプターを更新しました',
 | 
			
		||||
    'chapter_delete'              => 'がチャプターを削除:',
 | 
			
		||||
    'chapter_delete_notification' => 'チャプターを削除しました',
 | 
			
		||||
    'chapter_move'                => 'がチャプターを移動:',
 | 
			
		||||
 | 
			
		||||
    // Books
 | 
			
		||||
    'book_create'                 => 'がブックを作成:',
 | 
			
		||||
    'book_create_notification'    => 'ブックを作成しました',
 | 
			
		||||
    'book_update'                 => 'がブックを更新:',
 | 
			
		||||
    'book_update_notification'    => 'ブックを更新しました',
 | 
			
		||||
    'book_delete'                 => 'がブックを削除:',
 | 
			
		||||
    'book_delete_notification'    => 'ブックを削除しました',
 | 
			
		||||
    'book_sort'                   => 'がブックの並び順を変更:',
 | 
			
		||||
    'book_sort_notification'      => '並び順を変更しました',
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
<?php
 | 
			
		||||
return [
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Authentication Language Lines
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | The following language lines are used during authentication for various
 | 
			
		||||
    | messages that we need to display to the user. You are free to modify
 | 
			
		||||
    | these language lines according to your application's requirements.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
    'failed' => 'この資格情報は登録されていません。',
 | 
			
		||||
    'throttle' => 'ログイン試行回数が制限を超えました。:seconds秒後に再試行してください。',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Login & Register
 | 
			
		||||
     */
 | 
			
		||||
    'sign_up' => '新規登録',
 | 
			
		||||
    'log_in' => 'ログイン',
 | 
			
		||||
    'log_in_with' => ':socialDriverでログイン',
 | 
			
		||||
    'sign_up_with' => ':socialDriverで登録',
 | 
			
		||||
    'logout' => 'ログアウト',
 | 
			
		||||
 | 
			
		||||
    'name' => '名前',
 | 
			
		||||
    'username' => 'ユーザ名',
 | 
			
		||||
    'email' => 'メールアドレス',
 | 
			
		||||
    'password' => 'パスワード',
 | 
			
		||||
    'password_confirm' => 'パスワード (確認)',
 | 
			
		||||
    'password_hint' => '5文字以上である必要があります',
 | 
			
		||||
    'forgot_password' => 'パスワードをお忘れですか?',
 | 
			
		||||
    'remember_me' => 'ログイン情報を保存する',
 | 
			
		||||
    'ldap_email_hint' => 'このアカウントで使用するEメールアドレスを入力してください。',
 | 
			
		||||
    'create_account' => 'アカウント作成',
 | 
			
		||||
    'social_login' => 'SNSログイン',
 | 
			
		||||
    'social_registration' => 'SNS登録',
 | 
			
		||||
    'social_registration_text' => '他のサービスで登録 / ログインする',
 | 
			
		||||
 | 
			
		||||
    'register_thanks' => '登録が完了しました!',
 | 
			
		||||
    'register_confirm' => 'メール内の確認ボタンを押して、:appNameへアクセスしてください。',
 | 
			
		||||
    'registrations_disabled' => '登録は現在停止中です。',
 | 
			
		||||
    'registration_email_domain_invalid' => 'このEmailドメインでの登録は許可されていません。',
 | 
			
		||||
    'register_success' => '登録が完了し、ログインできるようになりました!',
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Password Reset
 | 
			
		||||
     */
 | 
			
		||||
    'reset_password' => 'パスワードリセット',
 | 
			
		||||
    'reset_password_send_instructions' => '以下にEメールアドレスを入力すると、パスワードリセットリンクが記載されたメールが送信されます。',
 | 
			
		||||
    'reset_password_send_button' => 'リセットリンクを送信',
 | 
			
		||||
    'reset_password_sent_success' => ':emailへリセットリンクを送信しました。',
 | 
			
		||||
    'reset_password_success' => 'パスワードがリセットされました。',
 | 
			
		||||
 | 
			
		||||
    'email_reset_subject' => ':appNameのパスワードをリセット',
 | 
			
		||||
    'email_reset_text' => 'このメールは、パスワードリセットがリクエストされたため送信されています。',
 | 
			
		||||
    'email_reset_not_requested' => 'もしパスワードリセットを希望しない場合、操作は不要です。',
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Email Confirmation
 | 
			
		||||
     */
 | 
			
		||||
    'email_confirm_subject' => ':appNameのメールアドレス確認',
 | 
			
		||||
    'email_confirm_greeting' => ':appNameへ登録してくださりありがとうございます!',
 | 
			
		||||
    'email_confirm_text' => '以下のボタンを押し、メールアドレスを確認してください:',
 | 
			
		||||
    'email_confirm_action' => 'メールアドレスを確認',
 | 
			
		||||
    'email_confirm_send_error' => 'Eメールの確認が必要でしたが、システム上でEメールの送信ができませんでした。管理者に連絡し、Eメールが正しく設定されていることを確認してください。',
 | 
			
		||||
    'email_confirm_success' => 'Eメールアドレスが確認されました。',
 | 
			
		||||
    'email_confirm_resent' => '確認メールを再送信しました。受信トレイを確認してください。',
 | 
			
		||||
 | 
			
		||||
    'email_not_confirmed' => 'Eメールアドレスが確認できていません',
 | 
			
		||||
    'email_not_confirmed_text' => 'Eメールアドレスの確認が完了していません。',
 | 
			
		||||
    'email_not_confirmed_click_link' => '登録時に受信したメールを確認し、確認リンクをクリックしてください。',
 | 
			
		||||
    'email_not_confirmed_resend' => 'Eメールが見つからない場合、以下のフォームから再送信してください。',
 | 
			
		||||
    'email_not_confirmed_resend_button' => '確認メールを再送信',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
<?php
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Buttons
 | 
			
		||||
     */
 | 
			
		||||
    'cancel' => 'キャンセル',
 | 
			
		||||
    'confirm' => '確認',
 | 
			
		||||
    'back' => '戻る',
 | 
			
		||||
    'save' => '保存',
 | 
			
		||||
    'continue' => '続ける',
 | 
			
		||||
    'select' => '選択',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Form Labels
 | 
			
		||||
     */
 | 
			
		||||
    'name' => '名称',
 | 
			
		||||
    'description' => '概要',
 | 
			
		||||
    'role' => '権限',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Actions
 | 
			
		||||
     */
 | 
			
		||||
    'actions' => '実行',
 | 
			
		||||
    'view' => '表示',
 | 
			
		||||
    'create' => '作成',
 | 
			
		||||
    'update' => '更新',
 | 
			
		||||
    'edit' => '編集',
 | 
			
		||||
    'sort' => '並び順',
 | 
			
		||||
    'move' => '移動',
 | 
			
		||||
    'delete' => '削除',
 | 
			
		||||
    'search' => '検索',
 | 
			
		||||
    'search_clear' => '検索をクリア',
 | 
			
		||||
    'reset' => 'リセット',
 | 
			
		||||
    'remove' => '削除',
 | 
			
		||||
    'add' => '追加',
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Misc
 | 
			
		||||
     */
 | 
			
		||||
    'deleted_user' => '削除済みユーザ',
 | 
			
		||||
    'no_activity' => '表示するアクティビティがありません',
 | 
			
		||||
    'no_items' => 'アイテムはありません',
 | 
			
		||||
    'back_to_top' => '上に戻る',
 | 
			
		||||
    'toggle_details' => '概要の表示切替',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Header
 | 
			
		||||
     */
 | 
			
		||||
    'view_profile' => 'プロフィール表示',
 | 
			
		||||
    'edit_profile' => 'プロフィール編集',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Email Content
 | 
			
		||||
     */
 | 
			
		||||
    'email_action_help' => '":actionText" をクリックできない場合、以下のURLをコピーしブラウザで開いてください:',
 | 
			
		||||
    'email_rights' => 'All rights reserved',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
<?php
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Image Manager
 | 
			
		||||
     */
 | 
			
		||||
    'image_select' => '画像を選択',
 | 
			
		||||
    'image_all' => 'すべて',
 | 
			
		||||
    'image_all_title' => '全ての画像を表示',
 | 
			
		||||
    'image_book_title' => 'このブックにアップロードされた画像を表示',
 | 
			
		||||
    'image_page_title' => 'このページにアップロードされた画像を表示',
 | 
			
		||||
    'image_search_hint' => '画像名で検索',
 | 
			
		||||
    'image_uploaded' => 'アップロード日時: :uploadedDate',
 | 
			
		||||
    'image_load_more' => 'さらに読み込む',
 | 
			
		||||
    'image_image_name' => '画像名',
 | 
			
		||||
    'image_delete_confirm' => 'この画像は以下のページで利用されています。削除してもよろしければ、再度ボタンを押して下さい。',
 | 
			
		||||
    'image_select_image' => '選択',
 | 
			
		||||
    'image_dropzone' => '画像をドロップするか、クリックしてアップロード',
 | 
			
		||||
    'images_deleted' => '画像を削除しました',
 | 
			
		||||
    'image_preview' => '画像プレビュー',
 | 
			
		||||
    'image_upload_success' => '画像がアップロードされました',
 | 
			
		||||
    'image_update_success' => '画像が更新されました',
 | 
			
		||||
    'image_delete_success' => '画像が削除されました'
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,236 @@
 | 
			
		|||
<?php
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shared
 | 
			
		||||
     */
 | 
			
		||||
    'recently_created' => '最近作成',
 | 
			
		||||
    'recently_created_pages' => '最近作成されたページ',
 | 
			
		||||
    'recently_updated_pages' => '最近更新されたページ',
 | 
			
		||||
    'recently_created_chapters' => '最近作成されたチャプター',
 | 
			
		||||
    'recently_created_books' => '最近作成されたブック',
 | 
			
		||||
    'recently_update' => '最近更新',
 | 
			
		||||
    'recently_viewed' => '閲覧履歴',
 | 
			
		||||
    'recent_activity' => 'アクティビティ',
 | 
			
		||||
    'create_now' => '作成する',
 | 
			
		||||
    'revisions' => '編集履歴',
 | 
			
		||||
    'meta_revision' => 'リビジョン #:revisionCount',
 | 
			
		||||
    'meta_created' => '作成: :timeLength',
 | 
			
		||||
    'meta_created_name' => '作成: :timeLength (:user)',
 | 
			
		||||
    'meta_updated' => '更新: :timeLength',
 | 
			
		||||
    'meta_updated_name' => '更新: :timeLength (:user)',
 | 
			
		||||
    'x_pages' => ':countページ',
 | 
			
		||||
    'entity_select' => 'エンティティ選択',
 | 
			
		||||
    'images' => '画像',
 | 
			
		||||
    'my_recent_drafts' => '最近の下書き',
 | 
			
		||||
    'my_recently_viewed' => '閲覧履歴',
 | 
			
		||||
    'no_pages_viewed' => 'なにもページを閲覧していません',
 | 
			
		||||
    'no_pages_recently_created' => '最近作成されたページはありません',
 | 
			
		||||
    'no_pages_recently_updated' => '最近更新されたページはありません。',
 | 
			
		||||
    'export' => 'エクスポート',
 | 
			
		||||
    'export_html' => 'Webページ',
 | 
			
		||||
    'export_pdf' => 'PDF',
 | 
			
		||||
    'export_text' => 'テキストファイル',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Permissions and restrictions
 | 
			
		||||
     */
 | 
			
		||||
    'permissions' => '権限',
 | 
			
		||||
    'permissions_intro' => 'この設定は各ユーザの役割よりも優先して適用されます。',
 | 
			
		||||
    'permissions_enable' => 'カスタム権限設定を有効にする',
 | 
			
		||||
    'permissions_save' => '権限を保存',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Search
 | 
			
		||||
     */
 | 
			
		||||
    'search_results' => '検索結果',
 | 
			
		||||
    'search_total_results_found' => ':count件見つかりました',
 | 
			
		||||
    'search_clear' => '検索をクリア',
 | 
			
		||||
    'search_no_pages' => 'ページが見つかりませんでした。',
 | 
			
		||||
    'search_for_term' => ':term の検索結果',
 | 
			
		||||
    'search_more' => 'さらに表示',
 | 
			
		||||
    'search_filters' => '検索フィルタ',
 | 
			
		||||
    'search_content_type' => '種類',
 | 
			
		||||
    'search_exact_matches' => '完全一致',
 | 
			
		||||
    'search_tags' => 'タグ検索',
 | 
			
		||||
    'search_viewed_by_me' => '自分が閲覧したことがある',
 | 
			
		||||
    'search_not_viewed_by_me' => '自分が閲覧したことがない',
 | 
			
		||||
    'search_permissions_set' => '権限が設定されている',
 | 
			
		||||
    'search_created_by_me' => '自分が作成した',
 | 
			
		||||
    'search_updated_by_me' => '自分が更新した',
 | 
			
		||||
    'search_updated_before' => '以前に更新',
 | 
			
		||||
    'search_updated_after' => '以降に更新',
 | 
			
		||||
    'search_created_before' => '以前に作成',
 | 
			
		||||
    'search_created_after' => '以降に更新',
 | 
			
		||||
    'search_set_date' => '日付を設定',
 | 
			
		||||
    'search_update' => 'フィルタを更新',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Books
 | 
			
		||||
     */
 | 
			
		||||
    'book' => 'Book',
 | 
			
		||||
    'books' => 'ブック',
 | 
			
		||||
    'books_empty' => 'まだブックは作成されていません',
 | 
			
		||||
    'books_popular' => '人気のブック',
 | 
			
		||||
    'books_recent' => '最近のブック',
 | 
			
		||||
    'books_popular_empty' => 'ここに人気のブックが表示されます。',
 | 
			
		||||
    'books_create' => '新しいブックを作成',
 | 
			
		||||
    'books_delete' => 'ブックを削除',
 | 
			
		||||
    'books_delete_named' => 'ブック「:bookName」を削除',
 | 
			
		||||
    'books_delete_explain' => '「:bookName」を削除すると、ブック内のページとチャプターも削除されます。',
 | 
			
		||||
    'books_delete_confirmation' => '本当にこのブックを削除してよろしいですか?',
 | 
			
		||||
    'books_edit' => 'ブックを編集',
 | 
			
		||||
    'books_edit_named' => 'ブック「:bookName」を編集',
 | 
			
		||||
    'books_form_book_name' => 'ブック名',
 | 
			
		||||
    'books_save' => 'ブックを保存',
 | 
			
		||||
    'books_permissions' => 'ブックの権限',
 | 
			
		||||
    'books_permissions_updated' => 'ブックの権限を更新しました',
 | 
			
		||||
    'books_empty_contents' => 'まだページまたはチャプターが作成されていません。',
 | 
			
		||||
    'books_empty_create_page' => '新しいページを作成',
 | 
			
		||||
    'books_empty_or' => 'または',
 | 
			
		||||
    'books_empty_sort_current_book' => 'ブックの並び順を変更',
 | 
			
		||||
    'books_empty_add_chapter' => 'チャプターを追加',
 | 
			
		||||
    'books_permissions_active' => 'ブックの権限は有効です',
 | 
			
		||||
    'books_search_this' => 'このブックから検索',
 | 
			
		||||
    'books_navigation' => '目次',
 | 
			
		||||
    'books_sort' => '並び順を変更',
 | 
			
		||||
    'books_sort_named' => 'ブック「:bookName」を並び替え',
 | 
			
		||||
    'books_sort_show_other' => '他のブックを表示',
 | 
			
		||||
    'books_sort_save' => '並び順を保存',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Chapters
 | 
			
		||||
     */
 | 
			
		||||
    'chapter' => 'チャプター',
 | 
			
		||||
    'chapters' => 'チャプター',
 | 
			
		||||
    'chapters_popular' => '人気のチャプター',
 | 
			
		||||
    'chapters_new' => 'チャプターを作成',
 | 
			
		||||
    'chapters_create' => 'チャプターを作成',
 | 
			
		||||
    'chapters_delete' => 'チャプターを削除',
 | 
			
		||||
    'chapters_delete_named' => 'チャプター「:chapterName」を削除',
 | 
			
		||||
    'chapters_delete_explain' => 'チャプター「:chapterName」を削除すると、チャプター内のすべてのページはブック内に直接追加されます。',
 | 
			
		||||
    'chapters_delete_confirm' => 'チャプターを削除してよろしいですか?',
 | 
			
		||||
    'chapters_edit' => 'チャプターを編集',
 | 
			
		||||
    'chapters_edit_named' => 'チャプター「:chapterName」を編集',
 | 
			
		||||
    'chapters_save' => 'チャプターを保存',
 | 
			
		||||
    'chapters_move' => 'チャプターを移動',
 | 
			
		||||
    'chapters_move_named' => 'チャプター「:chapterName」を移動',
 | 
			
		||||
    'chapter_move_success' => 'チャプターを「:bookName」に移動しました',
 | 
			
		||||
    'chapters_permissions' => 'チャプター権限',
 | 
			
		||||
    'chapters_empty' => 'まだチャプター内にページはありません。',
 | 
			
		||||
    'chapters_permissions_active' => 'チャプターの権限は有効です',
 | 
			
		||||
    'chapters_permissions_success' => 'チャプターの権限を更新しました',
 | 
			
		||||
    'chapters_search_this' => 'このチャプターを検索',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Pages
 | 
			
		||||
     */
 | 
			
		||||
    'page' => 'ページ',
 | 
			
		||||
    'pages' => 'ページ',
 | 
			
		||||
    'pages_popular' => '人気のページ',
 | 
			
		||||
    'pages_new' => 'ページを作成',
 | 
			
		||||
    'pages_attachments' => '添付',
 | 
			
		||||
    'pages_navigation' => 'ページナビゲーション',
 | 
			
		||||
    'pages_delete' => 'ページを削除',
 | 
			
		||||
    'pages_delete_named' => 'ページ :pageName を削除',
 | 
			
		||||
    'pages_delete_draft_named' => 'ページ :pageName の下書きを削除',
 | 
			
		||||
    'pages_delete_draft' => 'ページの下書きを削除',
 | 
			
		||||
    'pages_delete_success' => 'ページを削除しました',
 | 
			
		||||
    'pages_delete_draft_success' => 'ページの下書きを削除しました',
 | 
			
		||||
    'pages_delete_confirm' => 'このページを削除してもよろしいですか?',
 | 
			
		||||
    'pages_delete_draft_confirm' => 'このページの下書きを削除してもよろしいですか?',
 | 
			
		||||
    'pages_editing_named' => 'ページ :pageName を編集',
 | 
			
		||||
    'pages_edit_toggle_header' => 'ヘッダーの表示切替',
 | 
			
		||||
    'pages_edit_save_draft' => '下書きを保存',
 | 
			
		||||
    'pages_edit_draft' => 'ページの下書きを編集',
 | 
			
		||||
    'pages_editing_draft' => '下書きを編集中',
 | 
			
		||||
    'pages_editing_page' => 'ページを編集中',
 | 
			
		||||
    'pages_edit_draft_save_at' => '下書きを保存済み: ',
 | 
			
		||||
    'pages_edit_delete_draft' => '下書きを削除',
 | 
			
		||||
    'pages_edit_discard_draft' => '下書きを破棄',
 | 
			
		||||
    'pages_edit_set_changelog' => '編集内容についての説明',
 | 
			
		||||
    'pages_edit_enter_changelog_desc' => 'どのような変更を行ったのかを記録してください',
 | 
			
		||||
    'pages_edit_enter_changelog' => '編集内容を入力',
 | 
			
		||||
    'pages_save' => 'ページを保存',
 | 
			
		||||
    'pages_title' => 'ページタイトル',
 | 
			
		||||
    'pages_name' => 'ページ名',
 | 
			
		||||
    'pages_md_editor' => 'エディター',
 | 
			
		||||
    'pages_md_preview' => 'プレビュー',
 | 
			
		||||
    'pages_md_insert_image' => '画像を挿入',
 | 
			
		||||
    'pages_md_insert_link' => 'エンティティへのリンクを挿入',
 | 
			
		||||
    'pages_not_in_chapter' => 'チャプターが設定されていません',
 | 
			
		||||
    'pages_move' => 'ページを移動',
 | 
			
		||||
    'pages_move_success' => 'ページを ":parentName" へ移動しました',
 | 
			
		||||
    'pages_permissions' => 'ページの権限設定',
 | 
			
		||||
    'pages_permissions_success' => 'ページの権限を更新しました',
 | 
			
		||||
    'pages_revisions' => '編集履歴',
 | 
			
		||||
    'pages_revisions_named' => ':pageName のリビジョン',
 | 
			
		||||
    'pages_revision_named' => ':pageName のリビジョン',
 | 
			
		||||
    'pages_revisions_created_by' => '作成者',
 | 
			
		||||
    'pages_revisions_date' => '日付',
 | 
			
		||||
    'pages_revisions_number' => 'リビジョン',
 | 
			
		||||
    'pages_revisions_changelog' => '説明',
 | 
			
		||||
    'pages_revisions_changes' => '変更点',
 | 
			
		||||
    'pages_revisions_current' => '現在のバージョン',
 | 
			
		||||
    'pages_revisions_preview' => 'プレビュー',
 | 
			
		||||
    'pages_revisions_restore' => '復元',
 | 
			
		||||
    'pages_revisions_none' => 'このページにはリビジョンがありません',
 | 
			
		||||
    'pages_copy_link' => 'リンクをコピー',
 | 
			
		||||
    'pages_permissions_active' => 'ページの権限は有効です',
 | 
			
		||||
    'pages_initial_revision' => '初回の公開',
 | 
			
		||||
    'pages_initial_name' => '新規ページ',
 | 
			
		||||
    'pages_editing_draft_notification' => ':timeDiffに保存された下書きを編集しています。',
 | 
			
		||||
    'pages_draft_edited_notification' => 'このページは更新されています。下書きを破棄することを推奨します。',
 | 
			
		||||
    'pages_draft_edit_active' => [
 | 
			
		||||
        'start_a' => ':count人のユーザがページの編集を開始しました',
 | 
			
		||||
        'start_b' => ':userNameがページの編集を開始しました',
 | 
			
		||||
        'time_a' => '数秒前に保存されました',
 | 
			
		||||
        'time_b' => ':minCount分前に保存されました',
 | 
			
		||||
        'message' => ':start :time. 他のユーザによる更新を上書きしないよう注意してください。',
 | 
			
		||||
    ],
 | 
			
		||||
    'pages_draft_discarded' => '下書きが破棄されました。エディタは現在の内容へ復元されています。',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Editor sidebar
 | 
			
		||||
     */
 | 
			
		||||
    'page_tags' => 'タグ',
 | 
			
		||||
    'tag' => 'タグ',
 | 
			
		||||
    'tags' =>  '',
 | 
			
		||||
    'tag_value' => '内容 (オプション)',
 | 
			
		||||
    'tags_explain' => "タグを設定すると、コンテンツの管理が容易になります。\nより高度な管理をしたい場合、タグに内容を設定できます。",
 | 
			
		||||
    'tags_add' => 'タグを追加',
 | 
			
		||||
    'attachments' => '添付ファイル',
 | 
			
		||||
    'attachments_explain' => 'ファイルをアップロードまたはリンクを添付することができます。これらはサイドバーで確認できます。',
 | 
			
		||||
    'attachments_explain_instant_save' => 'この変更は即座に保存されます。',
 | 
			
		||||
    'attachments_items' => 'アイテム',
 | 
			
		||||
    'attachments_upload' => 'アップロード',
 | 
			
		||||
    'attachments_link' => 'リンクを添付',
 | 
			
		||||
    'attachments_set_link' => 'リンクを設定',
 | 
			
		||||
    'attachments_delete_confirm' => 'もう一度クリックし、削除を確認してください。',
 | 
			
		||||
    'attachments_dropzone' => 'ファイルをドロップするか、クリックして選択',
 | 
			
		||||
    'attachments_no_files' => 'ファイルはアップロードされていません',
 | 
			
		||||
    'attachments_explain_link' => 'ファイルをアップロードしたくない場合、他のページやクラウド上のファイルへのリンクを添付できます。',
 | 
			
		||||
    'attachments_link_name' => 'リンク名',
 | 
			
		||||
    'attachment_link' => '添付リンク',
 | 
			
		||||
    'attachments_link_url' => 'ファイルURL',
 | 
			
		||||
    'attachments_link_url_hint' => 'WebサイトまたはファイルへのURL',
 | 
			
		||||
    'attach' => '添付',
 | 
			
		||||
    'attachments_edit_file' => 'ファイルを編集',
 | 
			
		||||
    'attachments_edit_file_name' => 'ファイル名',
 | 
			
		||||
    'attachments_edit_drop_upload' => 'ファイルをドロップするか、クリックしてアップロード',
 | 
			
		||||
    'attachments_order_updated' => '添付ファイルの並び順が変更されました',
 | 
			
		||||
    'attachments_updated_success' => '添付ファイルが更新されました',
 | 
			
		||||
    'attachments_deleted' => '添付は削除されました',
 | 
			
		||||
    'attachments_file_uploaded' => 'ファイルがアップロードされました',
 | 
			
		||||
    'attachments_file_updated' => 'ファイルが更新されました',
 | 
			
		||||
    'attachments_link_attached' => 'リンクがページへ添付されました',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Profile View
 | 
			
		||||
     */
 | 
			
		||||
    'profile_user_for_x' => ':time前に作成',
 | 
			
		||||
    'profile_created_content' => '作成したコンテンツ',
 | 
			
		||||
    'profile_not_created_pages' => ':userNameはページを作成していません',
 | 
			
		||||
    'profile_not_created_chapters' => ':userNameはチャプターを作成していません',
 | 
			
		||||
    'profile_not_created_books' => ':userNameはブックを作成していません',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Error text strings.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    // Permissions
 | 
			
		||||
    'permission' => 'リクエストされたページへの権限がありません。',
 | 
			
		||||
    'permissionJson' => '要求されたアクションを実行する権限がありません。',
 | 
			
		||||
 | 
			
		||||
    // Auth
 | 
			
		||||
    'error_user_exists_different_creds' => ':emailを持つユーザは既に存在しますが、資格情報が異なります。',
 | 
			
		||||
    'email_already_confirmed' => 'Eメールは既に確認済みです。ログインしてください。',
 | 
			
		||||
    'email_confirmation_invalid' => 'この確認トークンは無効か、または既に使用済みです。登録を再試行してください。',
 | 
			
		||||
    'email_confirmation_expired' => '確認トークンは有効期限切れです。確認メールを再送しました。',
 | 
			
		||||
    'ldap_fail_anonymous' => '匿名バインドを用いたLDAPアクセスに失敗しました',
 | 
			
		||||
    'ldap_fail_authed' => '識別名, パスワードを用いたLDAPアクセスに失敗しました',
 | 
			
		||||
    'ldap_extension_not_installed' => 'LDAP PHP extensionがインストールされていません',
 | 
			
		||||
    'ldap_cannot_connect' => 'LDAPサーバに接続できませんでした',
 | 
			
		||||
    'social_no_action_defined' => 'アクションが定義されていません',
 | 
			
		||||
    'social_account_in_use' => ':socialAccountアカウントは既に使用されています。:socialAccountのオプションからログインを試行してください。',
 | 
			
		||||
    'social_account_email_in_use' => ':emailは既に使用されています。ログイン後、プロフィール設定から:socialAccountアカウントを接続できます。',
 | 
			
		||||
    'social_account_existing' => 'アカウント:socialAccountは既にあなたのプロフィールに接続されています。',
 | 
			
		||||
    'social_account_already_used_existing' => 'この:socialAccountアカウントは既に他のユーザが使用しています。',
 | 
			
		||||
    'social_account_not_used' => 'この:socialAccountアカウントはどのユーザにも接続されていません。プロフィール設定から接続できます。',
 | 
			
		||||
    'social_account_register_instructions' => 'まだアカウントをお持ちでない場合、:socialAccountオプションから登録できます。',
 | 
			
		||||
    'social_driver_not_found' => 'Social driverが見つかりません。',
 | 
			
		||||
    'social_driver_not_configured' => 'あなたの:socialAccount設定は正しく構成されていません。',
 | 
			
		||||
 | 
			
		||||
    // System
 | 
			
		||||
    'path_not_writable' => 'ファイルパス :filePath へアップロードできませんでした。サーバ上での書き込みを許可してください。',
 | 
			
		||||
    'cannot_get_image_from_url' => ':url から画像を取得できませんでした。',
 | 
			
		||||
    'cannot_create_thumbs' => 'このサーバはサムネイルを作成できません。GD PHP extensionがインストールされていることを確認してください。',
 | 
			
		||||
    'server_upload_limit' => 'このサイズの画像をアップロードすることは許可されていません。ファイルサイズを小さくし、再試行してください。',
 | 
			
		||||
    'image_upload_error' => '画像アップロード時にエラーが発生しました。',
 | 
			
		||||
 | 
			
		||||
    // Attachments
 | 
			
		||||
    'attachment_page_mismatch' => '添付を更新するページが一致しません',
 | 
			
		||||
 | 
			
		||||
    // Pages
 | 
			
		||||
    'page_draft_autosave_fail' => '下書きの保存に失敗しました。インターネットへ接続してください。',
 | 
			
		||||
 | 
			
		||||
    // Entities
 | 
			
		||||
    'entity_not_found' => 'エンティティが見つかりません',
 | 
			
		||||
    'book_not_found' => 'ブックが見つかりません',
 | 
			
		||||
    'page_not_found' => 'ページが見つかりません',
 | 
			
		||||
    'chapter_not_found' => 'チャプターが見つかりません',
 | 
			
		||||
    'selected_book_not_found' => '選択されたブックが見つかりません',
 | 
			
		||||
    'selected_book_chapter_not_found' => '選択されたブック、またはチャプターが見つかりません',
 | 
			
		||||
    'guests_cannot_save_drafts' => 'ゲストは下書きを保存できません',
 | 
			
		||||
 | 
			
		||||
    // Users
 | 
			
		||||
    'users_cannot_delete_only_admin' => '唯一の管理者を削除することはできません',
 | 
			
		||||
    'users_cannot_delete_guest' => 'ゲストユーザを削除することはできません',
 | 
			
		||||
 | 
			
		||||
    // Roles
 | 
			
		||||
    'role_cannot_be_edited' => 'この役割は編集できません',
 | 
			
		||||
    'role_system_cannot_be_deleted' => 'この役割はシステムで管理されているため、削除できません',
 | 
			
		||||
    'role_registration_default_cannot_delete' => 'この役割を登録時のデフォルトに設定することはできません',
 | 
			
		||||
 | 
			
		||||
    // Error pages
 | 
			
		||||
    '404_page_not_found' => 'ページが見つかりません',
 | 
			
		||||
    'sorry_page_not_found' => 'ページを見つけることができませんでした。',
 | 
			
		||||
    'return_home' => 'ホームに戻る',
 | 
			
		||||
    'error_occurred' => 'エラーが発生しました',
 | 
			
		||||
    'app_down' => ':appNameは現在停止しています',
 | 
			
		||||
    'back_soon' => '回復までしばらくお待ちください。',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Pagination Language Lines
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | The following language lines are used by the paginator library to build
 | 
			
		||||
    | the simple pagination links. You are free to change them to anything
 | 
			
		||||
    | you want to customize your views to better match your application.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'previous' => '« 前',
 | 
			
		||||
    'next'     => '次 »',
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Password Reminder Language Lines
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | The following language lines are the default lines which match reasons
 | 
			
		||||
    | that are given by the password broker for a password update attempt
 | 
			
		||||
    | has failed, such as for an invalid token or invalid new password.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'password' => 'パスワードは6文字以上である必要があります。',
 | 
			
		||||
    'user' => "このEメールアドレスに一致するユーザが見つかりませんでした。",
 | 
			
		||||
    'token' => 'このパスワードリセットトークンは無効です。',
 | 
			
		||||
    'sent' => 'パスワードリセットリンクを送信しました。',
 | 
			
		||||
    'reset' => 'パスワードはリセットされました。',
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,112 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Settings text strings
 | 
			
		||||
     * Contains all text strings used in the general settings sections of BookStack
 | 
			
		||||
     * including users and roles.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'settings' => '設定',
 | 
			
		||||
    'settings_save' => '設定を保存',
 | 
			
		||||
    'settings_save_success' => '設定を保存しました',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * App settings
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'app_settings' => 'アプリケーション設定',
 | 
			
		||||
    'app_name' => 'アプリケーション名',
 | 
			
		||||
    'app_name_desc' => 'この名前はヘッダーやEメール内で表示されます。',
 | 
			
		||||
    'app_name_header' => 'ヘッダーにアプリケーション名を表示する',
 | 
			
		||||
    'app_public_viewing' => 'アプリケーションを公開する',
 | 
			
		||||
    'app_secure_images' => '画像アップロード時のセキュリティを強化',
 | 
			
		||||
    'app_secure_images_desc' => 'パフォーマンスの観点から、全ての画像が公開になっています。このオプションを有効にすると、画像URLの先頭にランダムで推測困難な文字列が追加され、アクセスを困難にします。',
 | 
			
		||||
    'app_editor' => 'ページエディタ',
 | 
			
		||||
    'app_editor_desc' => 'ここで選択されたエディタを全ユーザが使用します。',
 | 
			
		||||
    'app_custom_html' => 'カスタムheadタグ',
 | 
			
		||||
    'app_custom_html_desc' => 'スタイルシートやアナリティクスコード追加したい場合、ここを編集します。これは<head>の最下部に挿入されます。',
 | 
			
		||||
    'app_logo' => 'ロゴ',
 | 
			
		||||
    'app_logo_desc' => '高さ43pxで表示されます。これを上回る場合、自動で縮小されます。',
 | 
			
		||||
    'app_primary_color' => 'プライマリカラー',
 | 
			
		||||
    'app_primary_color_desc' => '16進数カラーコードで入力します。空にした場合、デフォルトの色にリセットされます。',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registration settings
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'reg_settings' => '登録設定',
 | 
			
		||||
    'reg_allow' => '新規登録を許可',
 | 
			
		||||
    'reg_default_role' => '新規登録時のデフォルト役割',
 | 
			
		||||
    'reg_confirm_email' => 'Eメール認証を必須にする',
 | 
			
		||||
    'reg_confirm_email_desc' => 'ドメイン制限を有効にしている場合はEメール認証が必須となり、この項目は無視されます。',
 | 
			
		||||
    'reg_confirm_restrict_domain' => 'ドメイン制限',
 | 
			
		||||
    'reg_confirm_restrict_domain_desc' => '特定のドメインのみ登録できるようにする場合、以下にカンマ区切りで入力します。設定された場合、Eメール認証が必須になります。<br>登録後、ユーザは自由にEメールアドレスを変更できます。',
 | 
			
		||||
    'reg_confirm_restrict_domain_placeholder' => '制限しない',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Role settings
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'roles' => '役割',
 | 
			
		||||
    'role_user_roles' => '役割',
 | 
			
		||||
    'role_create' => '役割を作成',
 | 
			
		||||
    'role_create_success' => '役割を作成しました',
 | 
			
		||||
    'role_delete' => '役割を削除',
 | 
			
		||||
    'role_delete_confirm' => '役割「:roleName」を削除します。',
 | 
			
		||||
    'role_delete_users_assigned' => 'この役割は:userCount人のユーザに付与されています。該当するユーザを他の役割へ移行できます。',
 | 
			
		||||
    'role_delete_no_migration' => "ユーザを移行しない",
 | 
			
		||||
    'role_delete_sure' => '本当に役割を削除してよろしいですか?',
 | 
			
		||||
    'role_delete_success' => '役割を削除しました',
 | 
			
		||||
    'role_edit' => '役割を編集',
 | 
			
		||||
    'role_details' => '概要',
 | 
			
		||||
    'role_name' => '役割名',
 | 
			
		||||
    'role_desc' => '役割の説明',
 | 
			
		||||
    'role_system' => 'システム権限',
 | 
			
		||||
    'role_manage_users' => 'ユーザ管理',
 | 
			
		||||
    'role_manage_roles' => '役割と権限の管理',
 | 
			
		||||
    'role_manage_entity_permissions' => '全てのブック, チャプター, ページに対する権限の管理',
 | 
			
		||||
    'role_manage_own_entity_permissions' => '自身のブック, チャプター, ページに対する権限の管理',
 | 
			
		||||
    'role_manage_settings' => 'アプリケーション設定の管理',
 | 
			
		||||
    'role_asset' => 'アセット権限',
 | 
			
		||||
    'role_asset_desc' => '各アセットに対するデフォルトの権限を設定します。ここで設定した権限が優先されます。',
 | 
			
		||||
    'role_all' => '全て',
 | 
			
		||||
    'role_own' => '自身',
 | 
			
		||||
    'role_controlled_by_asset' => 'このアセットに対し、右記の操作を許可:',
 | 
			
		||||
    'role_save' => '役割を保存',
 | 
			
		||||
    'role_update_success' => '役割を更新しました',
 | 
			
		||||
    'role_users' => 'この役割を持つユーザ',
 | 
			
		||||
    'role_users_none' => 'この役割が付与されたユーザは居ません',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Users
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'users' => 'ユーザ',
 | 
			
		||||
    'user_profile' => 'ユーザプロフィール',
 | 
			
		||||
    'users_add_new' => 'ユーザを追加',
 | 
			
		||||
    'users_search' => 'ユーザ検索',
 | 
			
		||||
    'users_role' => 'ユーザ役割',
 | 
			
		||||
    'users_external_auth_id' => '外部認証ID',
 | 
			
		||||
    'users_password_warning' => 'パスワードを変更したい場合のみ入力してください',
 | 
			
		||||
    'users_system_public' => 'このユーザはアプリケーションにアクセスする全てのゲストを表します。ログインはできませんが、自動的に割り当てられます。',
 | 
			
		||||
    'users_delete' => 'ユーザを削除',
 | 
			
		||||
    'users_delete_named' => 'ユーザ「:userName」を削除',
 | 
			
		||||
    'users_delete_warning' => 'ユーザ「:userName」を完全に削除します。',
 | 
			
		||||
    'users_delete_confirm' => '本当にこのユーザを削除してよろしいですか?',
 | 
			
		||||
    'users_delete_success' => 'ユーザを削除しました',
 | 
			
		||||
    'users_edit' => 'ユーザ編集',
 | 
			
		||||
    'users_edit_profile' => 'プロフィール編集',
 | 
			
		||||
    'users_edit_success' => 'ユーザを更新しました',
 | 
			
		||||
    'users_avatar' => 'アバター',
 | 
			
		||||
    'users_avatar_desc' => '256pxの正方形である必要があります。',
 | 
			
		||||
    'users_preferred_language' => '使用言語',
 | 
			
		||||
    'users_social_accounts' => 'ソーシャルアカウント',
 | 
			
		||||
    'users_social_accounts_info' => 'アカウントを接続すると、ログインが簡単になります。ここでアカウントの接続を解除すると、そのアカウントを経由したログインを禁止できます。接続解除後、各ソーシャルアカウントの設定にてこのアプリケーションへのアクセス許可を解除してください。',
 | 
			
		||||
    'users_social_connect' => 'アカウントを接続',
 | 
			
		||||
    'users_social_disconnect' => 'アカウントを接続解除',
 | 
			
		||||
    'users_social_connected' => '「:socialAccount」がプロフィールに接続されました。',
 | 
			
		||||
    'users_social_disconnected' => '「:socialAccount」がプロフィールから接続解除されました。'
 | 
			
		||||
    
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,108 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Validation Language Lines
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | The following language lines contain the default error messages used by
 | 
			
		||||
    | the validator class. Some of these rules have multiple versions such
 | 
			
		||||
    | as the size rules. Feel free to tweak each of these messages here.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'accepted'             => ':attributeに同意する必要があります。',
 | 
			
		||||
    'active_url'           => ':attributeは正しいURLではありません。',
 | 
			
		||||
    'after'                => ':attributeは:date以降である必要があります。',
 | 
			
		||||
    'alpha'                => ':attributeは文字のみが含められます。',
 | 
			
		||||
    'alpha_dash'           => ':attributeは文字, 数値, ハイフンのみが含められます。',
 | 
			
		||||
    'alpha_num'            => ':attributeは文字と数値のみが含められます。',
 | 
			
		||||
    'array'                => ':attributeは配列である必要があります。',
 | 
			
		||||
    'before'               => ':attributeは:date以前である必要があります。',
 | 
			
		||||
    'between'              => [
 | 
			
		||||
        'numeric' => ':attributeは:min〜:maxである必要があります。',
 | 
			
		||||
        'file'    => ':attributeは:min〜:maxキロバイトである必要があります。',
 | 
			
		||||
        'string'  => ':attributeは:min〜:max文字である必要があります。',
 | 
			
		||||
        'array'   => ':attributeは:min〜:max個である必要があります。',
 | 
			
		||||
    ],
 | 
			
		||||
    'boolean'              => ':attributeはtrueまたはfalseである必要があります。',
 | 
			
		||||
    'confirmed'            => ':attributeの確認が一致しません。',
 | 
			
		||||
    'date'                 => ':attributeは正しい日時ではありません。',
 | 
			
		||||
    'date_format'          => ':attributeが:formatのフォーマットと一致しません。',
 | 
			
		||||
    'different'            => ':attributeと:otherは異なる必要があります。',
 | 
			
		||||
    'digits'               => ':attributeは:digitsデジットである必要があります',
 | 
			
		||||
    'digits_between'       => ':attributeは:min〜:maxである必要があります。',
 | 
			
		||||
    'email'                => ':attributeは正しいEメールアドレスである必要があります。',
 | 
			
		||||
    'filled'               => ':attributeは必須です。',
 | 
			
		||||
    'exists'               => '選択された:attributeは不正です。',
 | 
			
		||||
    'image'                => ':attributeは画像である必要があります。',
 | 
			
		||||
    'in'                   => '選択された:attributeは不正です。',
 | 
			
		||||
    'integer'              => ':attributeは数値である必要があります。',
 | 
			
		||||
    'ip'                   => ':attributeは正しいIPアドレスである必要があります。',
 | 
			
		||||
    'max'                  => [
 | 
			
		||||
        'numeric' => ':attributeは:maxを越えることができません。',
 | 
			
		||||
        'file'    => ':attributeは:maxキロバイトを越えることができません。',
 | 
			
		||||
        'string'  => ':attributeは:max文字をこえることができません。',
 | 
			
		||||
        'array'   => ':attributeは:max個を越えることができません。',
 | 
			
		||||
    ],
 | 
			
		||||
    'mimes'                => ':attributeのファイルタイプは以下のみが許可されています: :values.',
 | 
			
		||||
    'min'                  => [
 | 
			
		||||
        'numeric' => ':attributeは:min以上である必要があります。',
 | 
			
		||||
        'file'    => ':attributeは:minキロバイト以上である必要があります。',
 | 
			
		||||
        'string'  => ':attributeは:min文字以上である必要があります。',
 | 
			
		||||
        'array'   => ':attributeは:min個以上である必要があります。',
 | 
			
		||||
    ],
 | 
			
		||||
    'not_in'               => '選択された:attributeは不正です。',
 | 
			
		||||
    'numeric'              => ':attributeは数値である必要があります。',
 | 
			
		||||
    'regex'                => ':attributeのフォーマットは不正です。',
 | 
			
		||||
    'required'             => ':attributeは必須です。',
 | 
			
		||||
    'required_if'          => ':otherが:valueである場合、:attributeは必須です。',
 | 
			
		||||
    'required_with'        => ':valuesが設定されている場合、:attributeは必須です。',
 | 
			
		||||
    'required_with_all'    => ':valuesが設定されている場合、:attributeは必須です。',
 | 
			
		||||
    'required_without'     => ':valuesが設定されていない場合、:attributeは必須です。',
 | 
			
		||||
    'required_without_all' => ':valuesが設定されていない場合、:attributeは必須です。',
 | 
			
		||||
    'same'                 => ':attributeと:otherは一致している必要があります。',
 | 
			
		||||
    'size'                 => [
 | 
			
		||||
        'numeric' => ':attributeは:sizeである必要があります。',
 | 
			
		||||
        'file'    => ':attributeは:sizeキロバイトである必要があります。',
 | 
			
		||||
        'string'  => ':attributeは:size文字である必要があります。',
 | 
			
		||||
        'array'   => ':attributeは:size個である必要があります。',
 | 
			
		||||
    ],
 | 
			
		||||
    'string'               => ':attributeは文字列である必要があります。',
 | 
			
		||||
    'timezone'             => ':attributeは正しいタイムゾーンである必要があります。',
 | 
			
		||||
    'unique'               => ':attributeは既に使用されています。',
 | 
			
		||||
    'url'                  => ':attributeのフォーマットは不正です。',
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Custom Validation Language Lines
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | Here you may specify custom validation messages for attributes using the
 | 
			
		||||
    | convention "attribute.rule" to name the lines. This makes it quick to
 | 
			
		||||
    | specify a specific custom language line for a given attribute rule.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'custom' => [
 | 
			
		||||
        'password-confirm' => [
 | 
			
		||||
            'required_with' => 'パスワードの確認は必須です。',
 | 
			
		||||
        ],
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Custom Validation Attributes
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | The following language lines are used to swap attribute place-holders
 | 
			
		||||
    | with something more reader friendly such as E-Mail Address instead
 | 
			
		||||
    | of "email". This simply helps us make messages a little cleaner.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'attributes' => [],
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Activity text strings.
 | 
			
		||||
     * Is used for all the text within activity logs & notifications.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    // Pages
 | 
			
		||||
    'page_create'                 => 'utworzono stronę',
 | 
			
		||||
    'page_create_notification'    => 'Strona utworzona pomyślnie',
 | 
			
		||||
    'page_update'                 => 'zaktualizowano stronę',
 | 
			
		||||
    'page_update_notification'    => 'Strona zaktualizowana pomyślnie',
 | 
			
		||||
    'page_delete'                 => 'usunięto stronę',
 | 
			
		||||
    'page_delete_notification'    => 'Strona usunięta pomyślnie',
 | 
			
		||||
    'page_restore'                => 'przywrócono stronę',
 | 
			
		||||
    'page_restore_notification'   => 'Stronga przywrócona pomyślnie',
 | 
			
		||||
    'page_move'                   => 'przeniesiono stronę',
 | 
			
		||||
 | 
			
		||||
    // Chapters
 | 
			
		||||
    'chapter_create'              => 'utworzono rozdział',
 | 
			
		||||
    'chapter_create_notification' => 'Rozdział utworzony pomyślnie',
 | 
			
		||||
    'chapter_update'              => 'zaktualizowano rozdział',
 | 
			
		||||
    'chapter_update_notification' => 'Rozdział zaktualizowany pomyślnie',
 | 
			
		||||
    'chapter_delete'              => 'usunięto rozdział',
 | 
			
		||||
    'chapter_delete_notification' => 'Rozdział usunięty pomyślnie',
 | 
			
		||||
    'chapter_move'                => 'przeniesiono rozdział',
 | 
			
		||||
 | 
			
		||||
    // Books
 | 
			
		||||
    'book_create'                 => 'utworzono księgę',
 | 
			
		||||
    'book_create_notification'    => 'Księga utworzona pomyślnie',
 | 
			
		||||
    'book_update'                 => 'zaktualizowano księgę',
 | 
			
		||||
    'book_update_notification'    => 'Księga zaktualizowana pomyślnie',
 | 
			
		||||
    'book_delete'                 => 'usunięto księgę',
 | 
			
		||||
    'book_delete_notification'    => 'Księga usunięta pomyślnie',
 | 
			
		||||
    'book_sort'                   => 'posortowano księgę',
 | 
			
		||||
    'book_sort_notification'      => 'Księga posortowana pomyślnie',
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
<?php
 | 
			
		||||
return [
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Authentication Language Lines
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | The following language lines are used during authentication for various
 | 
			
		||||
    | messages that we need to display to the user. You are free to modify
 | 
			
		||||
    | these language lines according to your application's requirements.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
    'failed' => 'These credentials do not match our records.',
 | 
			
		||||
    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Login & Register
 | 
			
		||||
     */
 | 
			
		||||
    'sign_up' => 'Zarejestruj się',
 | 
			
		||||
    'log_in' => 'Zaloguj się',
 | 
			
		||||
    'log_in_with' => 'Zaloguj się za pomocą :socialDriver',
 | 
			
		||||
    'sign_up_with' => 'Zarejestruj się za pomocą :socialDriver',
 | 
			
		||||
    'logout' => 'Wyloguj',
 | 
			
		||||
 | 
			
		||||
    'name' => 'Imię',
 | 
			
		||||
    'username' => 'Nazwa użytkownika',
 | 
			
		||||
    'email' => 'Email',
 | 
			
		||||
    'password' => 'Hasło',
 | 
			
		||||
    'password_confirm' => 'Potwierdzenie hasła',
 | 
			
		||||
    'password_hint' => 'Musi mieć więcej niż 5 znaków',
 | 
			
		||||
    'forgot_password' => 'Przypomnij hasło',
 | 
			
		||||
    'remember_me' => 'Zapamiętaj mnie',
 | 
			
		||||
    'ldap_email_hint' => 'Wprowadź adres email dla tego konta.',
 | 
			
		||||
    'create_account' => 'Utwórz konto',
 | 
			
		||||
    'social_login' => 'Logowanie za pomocą konta społecznościowego',
 | 
			
		||||
    'social_registration' => 'Rejestracja za pomocą konta społecznościowego',
 | 
			
		||||
    'social_registration_text' => 'Zarejestruj się za pomocą innej usługi.',
 | 
			
		||||
 | 
			
		||||
    'register_thanks' => 'Dziękujemy za rejestrację!',
 | 
			
		||||
    'register_confirm' => 'Sprawdź podany adres e-mail i kliknij w link, by uzyskać dostęp do :appName.',
 | 
			
		||||
    'registrations_disabled' => 'Rejestracja jest obecnie zablokowana.',
 | 
			
		||||
    'registration_email_domain_invalid' => 'Adresy e-mail z tej domeny nie mają dostępu do tej aplikacji',
 | 
			
		||||
    'register_success' => 'Dziękujemy za rejestrację! Zalogowano Cię automatycznie.',
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Password Reset
 | 
			
		||||
     */
 | 
			
		||||
    'reset_password' => 'Resetowanie hasła',
 | 
			
		||||
    'reset_password_send_instructions' => 'Wprowadź adres e-mail powiązany z Twoim kontem, by otrzymać link do resetowania hasła.',
 | 
			
		||||
    'reset_password_send_button' => 'Wyślij link do resetowania hasła',
 | 
			
		||||
    'reset_password_sent_success' => 'Wysłano link do resetowania hasła na adres :email.',
 | 
			
		||||
    'reset_password_success' => 'Hasło zostało zresetowane pomyślnie.',
 | 
			
		||||
 | 
			
		||||
    'email_reset_subject' => 'Resetowanie hasła do :appName',
 | 
			
		||||
    'email_reset_text' => 'Otrzymujesz tę wiadomość ponieważ ktoś zażądał zresetowania hasła do Twojego konta.',
 | 
			
		||||
    'email_reset_not_requested' => 'Jeśli to nie Ty złożyłeś żądanie zresetowania hasła, zignoruj tę wiadomość.',
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Email Confirmation
 | 
			
		||||
     */
 | 
			
		||||
    'email_confirm_subject' => 'Potwierdź swój adres email w :appName',
 | 
			
		||||
    'email_confirm_greeting' => 'Dziękujemy za dołączenie do :appName!',
 | 
			
		||||
    'email_confirm_text' => 'Prosimy byś potwierdził swoje hasło klikając przycisk poniżej:',
 | 
			
		||||
    'email_confirm_action' => 'Potwierdź email',
 | 
			
		||||
    'email_confirm_send_error' => 'Wymagane jest potwierdzenie hasła, lecz wiadomość nie mogła zostać wysłana. Skontaktuj się z administratorem w celu upewnienia się, że skrzynka została skonfigurowana prawidłowo.',
 | 
			
		||||
    'email_confirm_success' => 'Adres email został potwierdzony!',
 | 
			
		||||
    'email_confirm_resent' => 'Wiadomość potwierdzająca została wysłana, sprawdź swoją skrzynkę.',
 | 
			
		||||
 | 
			
		||||
    'email_not_confirmed' => 'Adres email niepotwierdzony',
 | 
			
		||||
    'email_not_confirmed_text' => 'Twój adres email nie został jeszcze potwierdzony.',
 | 
			
		||||
    'email_not_confirmed_click_link' => 'Aby potwierdzić swoje konto kliknij w link wysłany w wiadomości po rejestracji.',
 | 
			
		||||
    'email_not_confirmed_resend' => 'Jeśli wiadomość do Ciebie nie dotarła możesz wysłać ją ponownie wypełniając formularz poniżej.',
 | 
			
		||||
    'email_not_confirmed_resend_button' => 'Wyślij ponownie wiadomość z potwierdzeniem',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
<?php
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Buttons
 | 
			
		||||
     */
 | 
			
		||||
    'cancel' => 'Anuluj',
 | 
			
		||||
    'confirm' => 'Zatwierdź',
 | 
			
		||||
    'back' => 'Wstecz',
 | 
			
		||||
    'save' => 'Zapisz',
 | 
			
		||||
    'continue' => 'Kontynuuj',
 | 
			
		||||
    'select' => 'Wybierz',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Form Labels
 | 
			
		||||
     */
 | 
			
		||||
    'name' => 'Nazwa',
 | 
			
		||||
    'description' => 'Opis',
 | 
			
		||||
    'role' => 'Rola',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Actions
 | 
			
		||||
     */
 | 
			
		||||
    'actions' => 'Akcje',
 | 
			
		||||
    'view' => 'Widok',
 | 
			
		||||
    'create' => 'Utwórz',
 | 
			
		||||
    'update' => 'Zaktualizuj',
 | 
			
		||||
    'edit' => 'Edytuj',
 | 
			
		||||
    'sort' => 'Sortuj',
 | 
			
		||||
    'move' => 'Przenieś',
 | 
			
		||||
    'delete' => 'Usuń',
 | 
			
		||||
    'search' => 'Szukaj',
 | 
			
		||||
    'search_clear' => 'Wyczyść wyszukiwanie',
 | 
			
		||||
    'reset' => 'Resetuj',
 | 
			
		||||
    'remove' => 'Usuń',
 | 
			
		||||
    'add' => 'Dodaj',
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Misc
 | 
			
		||||
     */
 | 
			
		||||
    'deleted_user' => 'Użytkownik usunięty',
 | 
			
		||||
    'no_activity' => 'Brak aktywności do pokazania',
 | 
			
		||||
    'no_items' => 'Brak elementów do wyświetlenia',
 | 
			
		||||
    'back_to_top' => 'Powrót na górę',
 | 
			
		||||
    'toggle_details' => 'Włącz/wyłącz szczegóły',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Header
 | 
			
		||||
     */
 | 
			
		||||
    'view_profile' => 'Zobacz profil',
 | 
			
		||||
    'edit_profile' => 'Edytuj profil',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Email Content
 | 
			
		||||
     */
 | 
			
		||||
    'email_action_help' => 'Jeśli masz problem z kliknięciem przycisku ":actionText", skopiuj i wklej poniższy adres URL w nowej karcie swojej przeglądarki:',
 | 
			
		||||
    'email_rights' => 'Wszelkie prawa zastrzeżone',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
<?php
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Image Manager
 | 
			
		||||
     */
 | 
			
		||||
    'image_select' => 'Wybór obrazka',
 | 
			
		||||
    'image_all' => 'Wszystkie',
 | 
			
		||||
    'image_all_title' => 'Zobacz wszystkie obrazki',
 | 
			
		||||
    'image_book_title' => 'Zobacz obrazki zapisane w tej księdze',
 | 
			
		||||
    'image_page_title' => 'Zobacz obrazki zapisane na tej stronie',
 | 
			
		||||
    'image_search_hint' => 'Szukaj po nazwie obrazka',
 | 
			
		||||
    '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_select_image' => 'Wybierz obrazek',
 | 
			
		||||
    'image_dropzone' => 'Upuść obrazki tutaj lub kliknij by wybrać obrazki do udostępnienia',
 | 
			
		||||
    'images_deleted' => 'Usunięte obrazki',
 | 
			
		||||
    'image_preview' => 'Podgląd obrazka',
 | 
			
		||||
    'image_upload_success' => 'Obrazek wysłany pomyślnie',
 | 
			
		||||
    'image_update_success' => 'Szczegóły obrazka zaktualizowane pomyślnie',
 | 
			
		||||
    'image_delete_success' => 'Obrazek usunięty pomyślnie',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Code editor
 | 
			
		||||
     */
 | 
			
		||||
    'code_editor' => 'Edytuj kod',
 | 
			
		||||
    'code_language' => 'Język kodu',
 | 
			
		||||
    'code_content' => 'Zawartość kodu',
 | 
			
		||||
    'code_save' => 'Zapisz kod',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,237 @@
 | 
			
		|||
<?php
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shared
 | 
			
		||||
     */
 | 
			
		||||
    'recently_created' => 'Ostatnio utworzone',
 | 
			
		||||
    'recently_created_pages' => 'Ostatnio utworzone strony',
 | 
			
		||||
    'recently_updated_pages' => 'Ostatnio zaktualizowane strony',
 | 
			
		||||
    'recently_created_chapters' => 'Ostatnio utworzone rozdziały',
 | 
			
		||||
    'recently_created_books' => 'Ostatnio utworzone księgi',
 | 
			
		||||
    'recently_update' => 'Ostatnio zaktualizowane',
 | 
			
		||||
    'recently_viewed' => 'Ostatnio wyświetlane',
 | 
			
		||||
    'recent_activity' => 'Ostatnia aktywność',
 | 
			
		||||
    'create_now' => 'Utwórz teraz',
 | 
			
		||||
    'revisions' => 'Rewizje',
 | 
			
		||||
    'meta_revision' => 'Rewizja #:revisionCount',
 | 
			
		||||
    'meta_created' => 'Utworzono :timeLength',
 | 
			
		||||
    'meta_created_name' => 'Utworzono :timeLength przez :user',
 | 
			
		||||
    'meta_updated' => 'Zaktualizowano :timeLength',
 | 
			
		||||
    'meta_updated_name' => 'Zaktualizowano :timeLength przez :user',
 | 
			
		||||
    'x_pages' => ':count stron',
 | 
			
		||||
    'entity_select' => 'Wybór encji',
 | 
			
		||||
    'images' => 'Obrazki',
 | 
			
		||||
    'my_recent_drafts' => 'Moje ostatnie szkice',
 | 
			
		||||
    'my_recently_viewed' => 'Moje ostatnio wyświetlane',
 | 
			
		||||
    'no_pages_viewed' => 'Nie wyświetlano żadnych stron',
 | 
			
		||||
    'no_pages_recently_created' => 'Nie utworzono ostatnio żadnych stron',
 | 
			
		||||
    'no_pages_recently_updated' => 'Nie zaktualizowano ostatnio żadnych stron',
 | 
			
		||||
    'export' => 'Eksportuj',
 | 
			
		||||
    'export_html' => 'Plik HTML',
 | 
			
		||||
    'export_pdf' => 'Plik PDF',
 | 
			
		||||
    'export_text' => 'Plik tekstowy',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Permissions and restrictions
 | 
			
		||||
     */
 | 
			
		||||
    'permissions' => 'Uprawnienia',
 | 
			
		||||
    'permissions_intro' => 'Jeśli odblokowane, te uprawnienia będą miały priorytet względem pozostałych ustawionych uprawnień ról.',
 | 
			
		||||
    'permissions_enable' => 'Odblokuj własne uprawnienia',
 | 
			
		||||
    'permissions_save' => 'Zapisz uprawnienia',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Search
 | 
			
		||||
     */
 | 
			
		||||
    'search_results' => 'Wyniki wyszukiwania',
 | 
			
		||||
    'search_total_results_found' => ':count znalezionych wyników|:count ogółem znalezionych wyników',
 | 
			
		||||
    'search_clear' => 'Wyczyść wyszukiwanie',
 | 
			
		||||
    'search_no_pages' => 'Brak stron spełniających zadane kryterium',
 | 
			
		||||
    'search_for_term' => 'Szukaj :term',
 | 
			
		||||
    'search_more' => 'Więcej wyników',
 | 
			
		||||
    'search_filters' => 'Filtry wyszukiwania',
 | 
			
		||||
    'search_content_type' => 'Rodziaj treści',
 | 
			
		||||
    'search_exact_matches' => 'Dokładne frazy',
 | 
			
		||||
    'search_tags' => 'Tagi wyszukiwania',
 | 
			
		||||
    'search_viewed_by_me' => 'Wyświetlone przeze mnie',
 | 
			
		||||
    'search_not_viewed_by_me' => 'Niewyświetlone przeze mnie',
 | 
			
		||||
    'search_permissions_set' => 'Zbiór uprawnień',
 | 
			
		||||
    'search_created_by_me' => 'Utworzone przeze mnie',
 | 
			
		||||
    'search_updated_by_me' => 'Zaktualizowane przeze mnie',
 | 
			
		||||
    'search_updated_before' => 'Zaktualizowane przed',
 | 
			
		||||
    'search_updated_after' => 'Zaktualizowane po',
 | 
			
		||||
    'search_created_before' => 'Utworzone przed',
 | 
			
		||||
    'search_created_after' => 'Utworzone po',
 | 
			
		||||
    'search_set_date' => 'Ustaw datę',
 | 
			
		||||
    'search_update' => 'Zaktualizuj wyszukiwanie',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Books
 | 
			
		||||
     */
 | 
			
		||||
    'book' => 'Księga',
 | 
			
		||||
    'books' => 'Księgi',
 | 
			
		||||
    'books_empty' => 'Brak utworzonych ksiąg',
 | 
			
		||||
    'books_popular' => 'Popularne księgi',
 | 
			
		||||
    'books_recent' => 'Ostatnie księgi',
 | 
			
		||||
    'books_popular_empty' => 'Najbardziej popularne księgi zostaną wyświetlone w tym miejscu.',
 | 
			
		||||
    'books_create' => 'Utwórz księgę',
 | 
			
		||||
    'books_delete' => 'Usuń księgę',
 | 
			
		||||
    'books_delete_named' => 'Usuń księgę :bookName',
 | 
			
		||||
    'books_delete_explain' => 'To spowoduje usunięcie księgi \':bookName\', Wszystkie strony i rozdziały zostaną usunięte.',
 | 
			
		||||
    'books_delete_confirmation' => 'Czy na pewno chcesz usunąc tę księgę?',
 | 
			
		||||
    'books_edit' => 'Edytuj księgę',
 | 
			
		||||
    'books_edit_named' => 'Edytuj księgę :bookName',
 | 
			
		||||
    'books_form_book_name' => 'Nazwa księgi',
 | 
			
		||||
    'books_save' => 'Zapisz księgę',
 | 
			
		||||
    'books_permissions' => 'Uprawnienia księgi',
 | 
			
		||||
    'books_permissions_updated' => 'Zaktualizowano uprawnienia księgi',
 | 
			
		||||
    'books_empty_contents' => 'Brak stron lub rozdziałów w tej księdze.',
 | 
			
		||||
    'books_empty_create_page' => 'Utwórz nową stronę',
 | 
			
		||||
    'books_empty_or' => 'lub',
 | 
			
		||||
    'books_empty_sort_current_book' => 'posortuj bieżącą księgę',
 | 
			
		||||
    'books_empty_add_chapter' => 'Dodaj rozdział',
 | 
			
		||||
    'books_permissions_active' => 'Uprawnienia księgi aktywne',
 | 
			
		||||
    'books_search_this' => 'Wyszukaj w tej księdze',
 | 
			
		||||
    'books_navigation' => 'Nawigacja po księdze',
 | 
			
		||||
    'books_sort' => 'Sortuj zawartość Księgi',
 | 
			
		||||
    'books_sort_named' => 'Sortuj księgę :bookName',
 | 
			
		||||
    'books_sort_show_other' => 'Pokaż inne księgi',
 | 
			
		||||
    'books_sort_save' => 'Zapisz nowy porządek',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Chapters
 | 
			
		||||
     */
 | 
			
		||||
    'chapter' => 'Rozdział',
 | 
			
		||||
    'chapters' => 'Rozdziały',
 | 
			
		||||
    'chapters_popular' => 'Popularne rozdziały',
 | 
			
		||||
    'chapters_new' => 'Nowy rozdział',
 | 
			
		||||
    'chapters_create' => 'Utwórz nowy rozdział',
 | 
			
		||||
    'chapters_delete' => 'Usuń rozdział',
 | 
			
		||||
    'chapters_delete_named' => 'Usuń rozdział :chapterName',
 | 
			
		||||
    'chapters_delete_explain' => 'To spowoduje usunięcie rozdziału \':chapterName\', Wszystkie strony zostaną usunięte
 | 
			
		||||
        i dodane bezpośrednio do księgi macierzystej.',
 | 
			
		||||
    'chapters_delete_confirm' => 'Czy na pewno chcesz usunąć ten rozdział?',
 | 
			
		||||
    'chapters_edit' => 'Edytuj rozdział',
 | 
			
		||||
    'chapters_edit_named' => 'Edytuj rozdział :chapterName',
 | 
			
		||||
    'chapters_save' => 'Zapisz rozdział',
 | 
			
		||||
    'chapters_move' => 'Przenieś rozdział',
 | 
			
		||||
    'chapters_move_named' => 'Przenieś rozdział :chapterName',
 | 
			
		||||
    'chapter_move_success' => 'Rozdział przeniesiony do :bookName',
 | 
			
		||||
    'chapters_permissions' => 'Uprawienia rozdziału',
 | 
			
		||||
    'chapters_empty' => 'Brak stron w tym rozdziale.',
 | 
			
		||||
    'chapters_permissions_active' => 'Uprawnienia rozdziału aktywne',
 | 
			
		||||
    'chapters_permissions_success' => 'Zaktualizowano uprawnienia rozdziału',
 | 
			
		||||
    'chapters_search_this' => 'Przeszukaj ten rozdział',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Pages
 | 
			
		||||
     */
 | 
			
		||||
    'page' => 'Strona',
 | 
			
		||||
    'pages' => 'Strony',
 | 
			
		||||
    'pages_popular' => 'Popularne strony',
 | 
			
		||||
    'pages_new' => 'Nowa strona',
 | 
			
		||||
    'pages_attachments' => 'Załączniki',
 | 
			
		||||
    'pages_navigation' => 'Nawigacja po stronie',
 | 
			
		||||
    'pages_delete' => 'Usuń stronę',
 | 
			
		||||
    'pages_delete_named' => 'Usuń stronę :pageName',
 | 
			
		||||
    'pages_delete_draft_named' => 'Usuń szkic strony :pageName',
 | 
			
		||||
    'pages_delete_draft' => 'Usuń szkic strony',
 | 
			
		||||
    'pages_delete_success' => 'Strona usunięta pomyślnie',
 | 
			
		||||
    'pages_delete_draft_success' => 'Szkic strony usunięty pomyślnie',
 | 
			
		||||
    'pages_delete_confirm' => 'Czy na pewno chcesz usunąć tę stron?',
 | 
			
		||||
    'pages_delete_draft_confirm' => 'Czy na pewno chcesz usunąć szkic strony?',
 | 
			
		||||
    'pages_editing_named' => 'Edytowanie strony :pageName',
 | 
			
		||||
    'pages_edit_toggle_header' => 'Włącz/wyłącz nagłówek',
 | 
			
		||||
    'pages_edit_save_draft' => 'Zapisz szkic',
 | 
			
		||||
    'pages_edit_draft' => 'Edytuj szkic strony',
 | 
			
		||||
    'pages_editing_draft' => 'Edytowanie szkicu strony',
 | 
			
		||||
    'pages_editing_page' => 'Edytowanie strony',
 | 
			
		||||
    'pages_edit_draft_save_at' => 'Szkic zapisany ',
 | 
			
		||||
    'pages_edit_delete_draft' => 'Usuń szkic',
 | 
			
		||||
    'pages_edit_discard_draft' => 'Porzuć szkic',
 | 
			
		||||
    'pages_edit_set_changelog' => 'Ustaw log zmian',
 | 
			
		||||
    'pages_edit_enter_changelog_desc' => 'Opisz zmiany, które zostały wprowadzone',
 | 
			
		||||
    'pages_edit_enter_changelog' => 'Wyświetl log zmian',
 | 
			
		||||
    'pages_save' => 'Zapisz stronę',
 | 
			
		||||
    'pages_title' => 'Tytuł strony',
 | 
			
		||||
    'pages_name' => 'Nazwa strony',
 | 
			
		||||
    'pages_md_editor' => 'Edytor',
 | 
			
		||||
    'pages_md_preview' => 'Podgląd',
 | 
			
		||||
    'pages_md_insert_image' => 'Wstaw obrazek',
 | 
			
		||||
    'pages_md_insert_link' => 'Wstaw łącze do encji',
 | 
			
		||||
    'pages_not_in_chapter' => 'Strona nie została umieszczona w rozdziale',
 | 
			
		||||
    'pages_move' => 'Przenieś stronę',
 | 
			
		||||
    'pages_move_success' => 'Strona przeniesiona do ":parentName"',
 | 
			
		||||
    'pages_permissions' => 'Uprawnienia strony',
 | 
			
		||||
    'pages_permissions_success' => 'Zaktualizowano uprawnienia strony',
 | 
			
		||||
    'pages_revisions' => 'Rewizje strony',
 | 
			
		||||
    'pages_revisions_named' => 'Rewizje strony :pageName',
 | 
			
		||||
    'pages_revision_named' => 'Rewizja stroony :pageName',
 | 
			
		||||
    'pages_revisions_created_by' => 'Utworzona przez',
 | 
			
		||||
    'pages_revisions_date' => 'Data rewizji',
 | 
			
		||||
    'pages_revisions_number' => '#',
 | 
			
		||||
    'pages_revisions_changelog' => 'Log zmian',
 | 
			
		||||
    'pages_revisions_changes' => 'Zmiany',
 | 
			
		||||
    'pages_revisions_current' => 'Obecna wersja',
 | 
			
		||||
    'pages_revisions_preview' => 'Podgląd',
 | 
			
		||||
    'pages_revisions_restore' => 'Przywróć',
 | 
			
		||||
    'pages_revisions_none' => 'Ta strona nie posiada żadnych rewizji',
 | 
			
		||||
    'pages_copy_link' => 'Kopiuj link',
 | 
			
		||||
    'pages_permissions_active' => 'Uprawnienia strony aktywne',
 | 
			
		||||
    'pages_initial_revision' => 'Wydanie pierwotne',
 | 
			
		||||
    'pages_initial_name' => 'Nowa strona',
 | 
			
		||||
    'pages_editing_draft_notification' => 'Edytujesz obecnie szkic, który był ostatnio zapisany :timeDiff.',
 | 
			
		||||
    'pages_draft_edited_notification' => 'Od tego czasu ta strona była zmieniana. Zalecane jest odrzucenie tego szkicu.',
 | 
			
		||||
    'pages_draft_edit_active' => [
 | 
			
		||||
        'start_a' => ':count użytkowników rozpoczęło edytowanie tej strony',
 | 
			
		||||
        'start_b' => ':userName edytuje stronę',
 | 
			
		||||
        'time_a' => ' od czasu ostatniej edycji',
 | 
			
		||||
        'time_b' => 'w ciągu ostatnich :minCount minut',
 | 
			
		||||
        'message' => ':start :time. Pamiętaj by nie nadpisywać czyichś zmian!',
 | 
			
		||||
    ],
 | 
			
		||||
    'pages_draft_discarded' => 'Szkic odrzucony, edytor został uzupełniony najnowszą wersją strony',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Editor sidebar
 | 
			
		||||
     */
 | 
			
		||||
    'page_tags' => 'Tagi strony',
 | 
			
		||||
    'tag' => 'Tag',
 | 
			
		||||
    'tags' =>  '',
 | 
			
		||||
    'tag_value' => 'Wartość tagu (opcjonalnie)',
 | 
			
		||||
    'tags_explain' => "Dodaj tagi by skategoryzować zawartość. \n W celu dokładniejszej organizacji zawartości możesz dodać wartości do tagów.",
 | 
			
		||||
    'tags_add' => 'Dodaj kolejny tag',
 | 
			
		||||
    'attachments' => 'Załączniki',
 | 
			
		||||
    'attachments_explain' => 'Udostępnij kilka plików lub załącz link. Będą one widoczne na marginesie strony.',
 | 
			
		||||
    'attachments_explain_instant_save' => 'Zmiany są zapisywane natychmiastowo.',
 | 
			
		||||
    'attachments_items' => 'Załączniki',
 | 
			
		||||
    'attachments_upload' => 'Dodaj plik',
 | 
			
		||||
    'attachments_link' => 'Dodaj link',
 | 
			
		||||
    'attachments_set_link' => 'Ustaw link',
 | 
			
		||||
    'attachments_delete_confirm' => 'Kliknij ponownie Usuń by potwierdzić usunięcie załącznika.',
 | 
			
		||||
    'attachments_dropzone' => 'Upuść pliki lub kliknij tutaj by udostępnić pliki',
 | 
			
		||||
    'attachments_no_files' => 'Nie udostępniono plików',
 | 
			
		||||
    'attachments_explain_link' => 'Możesz załączyć link jeśli nie chcesz udostępniać pliku. Może być to link do innej strony lub link do pliku w chmurze.',
 | 
			
		||||
    'attachments_link_name' => 'Nazwa linku',
 | 
			
		||||
    'attachment_link' => 'Link do załącznika',
 | 
			
		||||
    'attachments_link_url' => 'Link do pliku',
 | 
			
		||||
    'attachments_link_url_hint' => 'Strona lub plik',
 | 
			
		||||
    'attach' => 'Załącz',
 | 
			
		||||
    'attachments_edit_file' => 'Edytuj plik',
 | 
			
		||||
    'attachments_edit_file_name' => 'Nazwa pliku',
 | 
			
		||||
    'attachments_edit_drop_upload' => 'Upuść pliki lub kliknij tutaj by udostępnić pliki i nadpisać istniejące',
 | 
			
		||||
    'attachments_order_updated' => 'Kolejność załączników zaktualizowana',
 | 
			
		||||
    'attachments_updated_success' => 'Szczegóły załączników zaktualizowane',
 | 
			
		||||
    'attachments_deleted' => 'Załączniki usunięte',
 | 
			
		||||
    'attachments_file_uploaded' => 'Plik załączony pomyślnie',
 | 
			
		||||
    'attachments_file_updated' => 'Plik zaktualizowany pomyślnie',
 | 
			
		||||
    'attachments_link_attached' => 'Link pomyślnie dodany do strony',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Profile View
 | 
			
		||||
     */
 | 
			
		||||
    'profile_user_for_x' => 'Użytkownik od :time',
 | 
			
		||||
    'profile_created_content' => 'Utworzona zawartość',
 | 
			
		||||
    'profile_not_created_pages' => ':userName nie utworzył żadnych stron',
 | 
			
		||||
    'profile_not_created_chapters' => ':userName nie utworzył żadnych rozdziałów',
 | 
			
		||||
    'profile_not_created_books' => ':userName nie utworzył żadnych ksiąg',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Error text strings.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    // Permissions
 | 
			
		||||
    'permission' => 'Nie masz uprawnień do wyświetlenia tej strony.',
 | 
			
		||||
    'permissionJson' => 'Nie masz uprawnień do wykonania tej akcji.',
 | 
			
		||||
 | 
			
		||||
    // Auth
 | 
			
		||||
    'error_user_exists_different_creds' => 'Użytkownik o adresie :email już istnieje.',
 | 
			
		||||
    'email_already_confirmed' => 'Email został potwierdzony, spróbuj się zalogować.',
 | 
			
		||||
    'email_confirmation_invalid' => 'Ten token jest nieprawidłowy lub został już wykorzystany. Spróbuj zarejestrować się ponownie.',
 | 
			
		||||
    'email_confirmation_expired' => 'Ten token potwierdzający wygasł. Wysłaliśmy Ci kolejny.',
 | 
			
		||||
    'ldap_fail_anonymous' => 'Dostęp LDAP przy użyciu anonimowego powiązania nie powiódł się',
 | 
			
		||||
    'ldap_fail_authed' => 'Dostęp LDAP przy użyciu tego dn i hasła nie powiódł się',
 | 
			
		||||
    'ldap_extension_not_installed' => 'Rozszerzenie LDAP PHP nie zostało zainstalowane',
 | 
			
		||||
    'ldap_cannot_connect' => 'Nie można połączyć z serwerem LDAP, połączenie nie zostało ustanowione',
 | 
			
		||||
    'social_no_action_defined' => 'Brak zdefiniowanej akcji',
 | 
			
		||||
    'social_account_in_use' => 'To konto :socialAccount jest już w użyciu, spróbuj zalogować się za pomocą opcji :socialAccount.',
 | 
			
		||||
    'social_account_email_in_use' => 'Email :email jest już w użyciu. Jeśli masz już konto, połącz konto :socialAccount z poziomu ustawień profilu.',
 | 
			
		||||
    'social_account_existing' => 'Konto :socialAccount jest już połączone z Twoim profilem',
 | 
			
		||||
    'social_account_already_used_existing' => 'Konto :socialAccount jest już używane przez innego użytkownika.',
 | 
			
		||||
    'social_account_not_used' => 'To konto :socialAccount nie jest połączone z żadnym użytkownikiem. Połącz je ze swoim kontem w ustawieniach profilu. ',
 | 
			
		||||
    'social_account_register_instructions' => 'Jeśli nie masz jeszcze konta, możesz zarejestrować je używając opcji :socialAccount.',
 | 
			
		||||
    'social_driver_not_found' => 'Funkcja społecznościowa nie została odnaleziona',
 | 
			
		||||
    'social_driver_not_configured' => 'Ustawienia konta :socialAccount nie są poprawne.',
 | 
			
		||||
 | 
			
		||||
    // System
 | 
			
		||||
    'path_not_writable' => 'Zapis do ścieżki :filePath jest niemożliwy. Upewnij się że aplikacja ma prawa do zapisu w niej.',
 | 
			
		||||
    'cannot_get_image_from_url' => 'Nie można pobrać obrazka z :url',
 | 
			
		||||
    'cannot_create_thumbs' => 'Serwer nie może utworzyć miniaturek. Upewnij się że rozszerzenie GD PHP zostało zainstalowane.',
 | 
			
		||||
    'server_upload_limit' => 'Serwer nie pozwala na przyjęcie pliku o tym rozmiarze. Spróbuj udostępnić coś o mniejszym rozmiarze.',
 | 
			
		||||
    'image_upload_error' => 'Wystąpił błąd podczas udostępniania obrazka',
 | 
			
		||||
 | 
			
		||||
    // Attachments
 | 
			
		||||
    'attachment_page_mismatch' => 'Niezgodność stron podczas aktualizacji załącznika',
 | 
			
		||||
 | 
			
		||||
    // Pages
 | 
			
		||||
    'page_draft_autosave_fail' => 'Zapis szkicu nie powiódł się. Upewnij się że posiadasz połączenie z internetem.',
 | 
			
		||||
 | 
			
		||||
    // Entities
 | 
			
		||||
    'entity_not_found' => 'Encja nie została odnaleziona',
 | 
			
		||||
    'book_not_found' => 'Księga nie została odnaleziona',
 | 
			
		||||
    'page_not_found' => 'Strona nie została odnaleziona',
 | 
			
		||||
    'chapter_not_found' => 'Rozdział nie został odnaleziony',
 | 
			
		||||
    'selected_book_not_found' => 'Wybrana księga nie została odnaleziona',
 | 
			
		||||
    'selected_book_chapter_not_found' => 'Wybrana księga lub rozdział nie zostały odnalezione',
 | 
			
		||||
    'guests_cannot_save_drafts' => 'Goście nie mogą zapisywać szkiców',
 | 
			
		||||
 | 
			
		||||
    // Users
 | 
			
		||||
    'users_cannot_delete_only_admin' => 'Nie możesz usunąć jedynego administratora',
 | 
			
		||||
    'users_cannot_delete_guest' => 'Nie możesz usunąć użytkownika-gościa',
 | 
			
		||||
 | 
			
		||||
    // Roles
 | 
			
		||||
    'role_cannot_be_edited' => 'Ta rola nie może być edytowana',
 | 
			
		||||
    'role_system_cannot_be_deleted' => 'Ta rola jest rolą systemową i nie może zostać usunięta',
 | 
			
		||||
    'role_registration_default_cannot_delete' => 'Ta rola nie może zostać usunięta jeśli jest ustawiona jako domyślna rola użytkownika',
 | 
			
		||||
 | 
			
		||||
    // Error pages
 | 
			
		||||
    '404_page_not_found' => 'Strona nie została odnaleziona',
 | 
			
		||||
    'sorry_page_not_found' => 'Przepraszamy, ale strona której szukasz nie została odnaleziona.',
 | 
			
		||||
    'return_home' => 'Powrót do strony głównej',
 | 
			
		||||
    'error_occurred' => 'Wystąpił błąd',
 | 
			
		||||
    'app_down' => ':appName jest aktualnie wyłączona',
 | 
			
		||||
    'back_soon' => 'Niedługo zostanie uruchomiona ponownie.',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Pagination Language Lines
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | The following language lines are used by the paginator library to build
 | 
			
		||||
    | the simple pagination links. You are free to change them to anything
 | 
			
		||||
    | you want to customize your views to better match your application.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'previous' => '« Poprzednia',
 | 
			
		||||
    'next'     => 'Następna »',
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Password Reminder Language Lines
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | The following language lines are the default lines which match reasons
 | 
			
		||||
    | that are given by the password broker for a password update attempt
 | 
			
		||||
    | has failed, such as for an invalid token or invalid new password.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'password' => 'Hasło musi zawierać co najmniej 6 znaków i być zgodne z powtórzeniem.',
 | 
			
		||||
    'user' => "Nie znaleziono użytkownika o takim adresie email.",
 | 
			
		||||
    'token' => 'Ten token resetowania hasła jest nieprawidłowy.',
 | 
			
		||||
    'sent' => 'Wysłaliśmy Ci link do resetowania hasła!',
 | 
			
		||||
    'reset' => 'Twoje hasło zostało zresetowane!',
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,111 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Settings text strings
 | 
			
		||||
     * Contains all text strings used in the general settings sections of BookStack
 | 
			
		||||
     * including users and roles.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'settings' => 'Ustawienia',
 | 
			
		||||
    'settings_save' => 'Zapisz ustawienia',
 | 
			
		||||
    'settings_save_success' => 'Ustawienia zapisane',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * App settings
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'app_settings' => 'Ustawienia aplikacji',
 | 
			
		||||
    'app_name' => 'Nazwa aplikacji',
 | 
			
		||||
    'app_name_desc' => 'Ta nazwa jest wyświetlana w nagłówku i emailach.',
 | 
			
		||||
    'app_name_header' => 'Pokazać nazwę aplikacji w nagłówku?',
 | 
			
		||||
    'app_public_viewing' => 'Zezwolić na publiczne przeglądanie?',
 | 
			
		||||
    'app_secure_images' => 'Odblokować wyższe bezpieczeństwo obrazków?',
 | 
			
		||||
    'app_secure_images_desc' => 'Ze względów wydajnościowych wszystkie obrazki są publiczne. Ta opcja dodaje dodatkowy, trudny do zgadnienia losowy ciąg na początku nazwy obrazka. Upewnij się że indeksowanie ścieżek jest zablokowane, by uniknąć problemów z dostępem do obrazka.',
 | 
			
		||||
    'app_editor' => 'Edytor strony',
 | 
			
		||||
    'app_editor_desc' => 'Wybierz edytor używany przez użytkowników do edycji zawartości.',
 | 
			
		||||
    'app_custom_html' => 'Własna zawartość tagu <head>',
 | 
			
		||||
    'app_custom_html_desc' => 'Zawartość dodana tutaj zostanie dołączona do sekcji <head> każdej strony. Przydatne przy nadpisywaniu styli lub dodawaniu analityki.',
 | 
			
		||||
    'app_logo' => 'Logo aplikacji',
 | 
			
		||||
    'app_logo_desc' => 'Ten obrazek powinien mieć nie więcej niż 43px w pionie. <br>Większe obrazki będą skalowane w dół.',
 | 
			
		||||
    'app_primary_color' => 'Podstawowy kolor aplikacji',
 | 
			
		||||
    'app_primary_color_desc' => 'To powinna być wartość HEX. <br>Zostaw to pole puste, by powrócić do podstawowego koloru.',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registration settings
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'reg_settings' => 'Ustawienia rejestracji',
 | 
			
		||||
    'reg_allow' => 'Zezwolić na rejestrację?',
 | 
			
		||||
    'reg_default_role' => 'Domyślna rola użytkownika po rejestracji',
 | 
			
		||||
    'reg_confirm_email' => 'Wymagać potwierdzenia adresu email?',
 | 
			
		||||
    'reg_confirm_email_desc' => 'Jeśli restrykcje domenowe zostały uzupełnione potwierdzenie adresu stanie się konieczne, a poniższa wartośc zostanie zignorowana.',
 | 
			
		||||
    'reg_confirm_restrict_domain' => 'Restrykcje domenowe dot. adresu email',
 | 
			
		||||
    'reg_confirm_restrict_domain_desc' => 'Wprowadź listę domen adresów email rozdzieloną przecinkami, którym chciałbyś zezwolić na rejestrację. Wymusi to konieczność potwierdzenia adresu email przez użytkownika przed uzyskaniem dostępu do aplikacji. <br> Pamiętaj, że użytkownicy będą mogli zmienić adres email po rejestracji.',
 | 
			
		||||
    'reg_confirm_restrict_domain_placeholder' => 'Brak restrykcji',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Role settings
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'roles' => 'Role',
 | 
			
		||||
    'role_user_roles' => 'Role użytkownika',
 | 
			
		||||
    'role_create' => 'Utwórz nową rolę',
 | 
			
		||||
    'role_create_success' => 'Rola utworzona pomyślnie',
 | 
			
		||||
    'role_delete' => 'Usuń rolę',
 | 
			
		||||
    'role_delete_confirm' => 'To spowoduje usunięcie roli \':roleName\'.',
 | 
			
		||||
    'role_delete_users_assigned' => 'Tę rolę ma przypisanych :userCount użytkowników. Jeśli chcesz zmigrować użytkowników z tej roli, wybierz nową poniżej.',
 | 
			
		||||
    'role_delete_no_migration' => "Nie migruj użytkowników",
 | 
			
		||||
    'role_delete_sure' => 'Czy na pewno chcesz usunąć tę rolę?',
 | 
			
		||||
    'role_delete_success' => 'Rola usunięta pomyślnie',
 | 
			
		||||
    'role_edit' => 'Edytuj rolę',
 | 
			
		||||
    'role_details' => 'Szczegóły roli',
 | 
			
		||||
    'role_name' => 'Nazwa roli',
 | 
			
		||||
    'role_desc' => 'Krótki opis roli',
 | 
			
		||||
    'role_system' => 'Uprawnienia systemowe',
 | 
			
		||||
    'role_manage_users' => 'Zarządzanie użytkownikami',
 | 
			
		||||
    'role_manage_roles' => 'Zarządzanie rolami i uprawnieniami ról',
 | 
			
		||||
    'role_manage_entity_permissions' => 'Zarządzanie uprawnieniami ksiąg, rozdziałów i stron',
 | 
			
		||||
    'role_manage_own_entity_permissions' => 'Zarządzanie uprawnieniami własnych ksiąg, rozdziałów i stron',
 | 
			
		||||
    'role_manage_settings' => 'Zarządzanie ustawieniami aplikacji',
 | 
			
		||||
    'role_asset' => 'Zarządzanie zasobami',
 | 
			
		||||
    'role_asset_desc' => 'Te ustawienia kontrolują zarządzanie zasobami systemu. Uprawnienia ksiąg, rozdziałów i stron nadpisują te ustawienia.',
 | 
			
		||||
    'role_all' => 'Wszyscy',
 | 
			
		||||
    'role_own' => 'Własne',
 | 
			
		||||
    'role_controlled_by_asset' => 'Kontrolowane przez zasób, do którego zostały udostępnione',
 | 
			
		||||
    'role_save' => 'Zapisz rolę',
 | 
			
		||||
    'role_update_success' => 'Rola zapisana pomyślnie',
 | 
			
		||||
    'role_users' => 'Użytkownicy w tej roli',
 | 
			
		||||
    'role_users_none' => 'Brak użytkowników zapisanych do tej roli',
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Users
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    'users' => 'Użytkownicy',
 | 
			
		||||
    'user_profile' => 'Profil użytkownika',
 | 
			
		||||
    'users_add_new' => 'Dodaj użytkownika',
 | 
			
		||||
    'users_search' => 'Wyszukaj użytkownika',
 | 
			
		||||
    'users_role' => 'Role użytkownika',
 | 
			
		||||
    'users_external_auth_id' => 'Zewnętrzne ID autentykacji',
 | 
			
		||||
    'users_password_warning' => 'Wypełnij poniżej tylko jeśli chcesz zmienić swoje hasło:',
 | 
			
		||||
    'users_system_public' => 'Ten użytkownik reprezentuje każdego gościa odwiedzającego tę aplikację. Nie można się na niego zalogować, lecz jest przyznawany automatycznie.',
 | 
			
		||||
    'users_delete' => 'Usuń użytkownika',
 | 
			
		||||
    'users_delete_named' => 'Usuń :userName',
 | 
			
		||||
    'users_delete_warning' => 'To usunie użytkownika \':userName\' z systemu.',
 | 
			
		||||
    'users_delete_confirm' => 'Czy na pewno chcesz usunąć tego użytkownika?',
 | 
			
		||||
    'users_delete_success' => 'Użytkownik usunięty pomyślnie',
 | 
			
		||||
    'users_edit' => 'Edytuj użytkownika',
 | 
			
		||||
    'users_edit_profile' => 'Edytuj profil',
 | 
			
		||||
    'users_edit_success' => 'Użytkownik zaktualizowany pomyśłnie',
 | 
			
		||||
    'users_avatar' => 'Avatar użytkownika',
 | 
			
		||||
    'users_avatar_desc' => 'Ten obrazek powinien mieć 25px x 256px.',
 | 
			
		||||
    'users_preferred_language' => 'Preferowany język',
 | 
			
		||||
    'users_social_accounts' => 'Konta społecznościowe',
 | 
			
		||||
    'users_social_accounts_info' => 'Tutaj możesz połączyć kilka kont społecznościowych w celu łatwiejszego i szybszego logowania.',
 | 
			
		||||
    'users_social_connect' => 'Podłącz konto',
 | 
			
		||||
    'users_social_disconnect' => 'Odłącz konto',
 | 
			
		||||
    'users_social_connected' => ':socialAccount zostało dodane do Twojego profilu.',
 | 
			
		||||
    'users_social_disconnected' => ':socialAccount zostało odłączone od Twojego profilu.',
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,108 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Validation Language Lines
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | The following language lines contain the default error messages used by
 | 
			
		||||
    | the validator class. Some of these rules have multiple versions such
 | 
			
		||||
    | as the size rules. Feel free to tweak each of these messages here.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'accepted'             => ':attribute musi zostać zaakceptowany.',
 | 
			
		||||
    'active_url'           => ':attribute nie jest prawidłowym adresem URL.',
 | 
			
		||||
    'after'                => ':attribute musi być datą następującą po :date.',
 | 
			
		||||
    'alpha'                => ':attribute może zawierać wyłącznie litery.',
 | 
			
		||||
    'alpha_dash'           => ':attribute może zawierać wyłącznie litery, cyfry i myślniki.',
 | 
			
		||||
    'alpha_num'            => ':attribute może zawierać wyłącznie litery i cyfry.',
 | 
			
		||||
    'array'                => ':attribute musi być tablicą.',
 | 
			
		||||
    'before'               => ':attribute musi być datą poprzedzającą :date.',
 | 
			
		||||
    'between'              => [
 | 
			
		||||
        'numeric' => ':attribute musi zawierać się w przedziale od :min do :max.',
 | 
			
		||||
        'file'    => 'Waga :attribute musi zawierać się pomiędzy :min i :max kilobajtów.',
 | 
			
		||||
        'string'  => 'Długość :attribute musi zawierać się pomiędzy :min i :max.',
 | 
			
		||||
        'array'   => ':attribute musi mieć od :min do :max elementów.',
 | 
			
		||||
    ],
 | 
			
		||||
    'boolean'              => ':attribute musi być wartością prawda/fałsz.',
 | 
			
		||||
    'confirmed'            => ':attribute i potwierdzenie muszą być zgodne.',
 | 
			
		||||
    'date'                 => ':attribute nie jest prawidłową datą.',
 | 
			
		||||
    'date_format'          => ':attribute musi mieć format :format.',
 | 
			
		||||
    'different'            => ':attribute i :other muszą się różnić.',
 | 
			
		||||
    'digits'               => ':attribute musi mieć :digits cyfr.',
 | 
			
		||||
    'digits_between'       => ':attribute musi mieć od :min do :max cyfr.',
 | 
			
		||||
    'email'                => ':attribute musi być prawidłowym adresem e-mail.',
 | 
			
		||||
    'filled'               => ':attribute jest wymagany.',
 | 
			
		||||
    'exists'               => 'Wybrana wartość :attribute jest nieprawidłowa.',
 | 
			
		||||
    'image'                => ':attribute musi być obrazkiem.',
 | 
			
		||||
    'in'                   => 'Wybrana wartość :attribute jest nieprawidłowa.',
 | 
			
		||||
    'integer'              => ':attribute musi być liczbą całkowitą.',
 | 
			
		||||
    'ip'                   => ':attribute musi być prawidłowym adresem IP.',
 | 
			
		||||
    'max'                  => [
 | 
			
		||||
        'numeric' => 'Wartość :attribute nie może być większa niż :max.',
 | 
			
		||||
        'file'    => 'Wielkość :attribute nie może być większa niż :max kilobajtów.',
 | 
			
		||||
        'string'  => 'Długość :attribute nie może być większa niż :max znaków.',
 | 
			
		||||
        'array'   => 'Rozmiar :attribute nie może być większy niż :max elementów.',
 | 
			
		||||
    ],
 | 
			
		||||
    'mimes'                => ':attribute musi być plikiem typu: :values.',
 | 
			
		||||
    'min'                  => [
 | 
			
		||||
        'numeric' => 'Wartość :attribute nie może być mniejsza od :min.',
 | 
			
		||||
        'file'    => 'Wielkość :attribute nie może być mniejsza niż :min kilobajtów.',
 | 
			
		||||
        'string'  => 'Długość :attribute nie może być mniejsza niż :min znaków.',
 | 
			
		||||
        'array'   => 'Rozmiar :attribute musi posiadać co najmniej :min elementy.',
 | 
			
		||||
    ],
 | 
			
		||||
    'not_in'               => 'Wartość :attribute jest nieprawidłowa.',
 | 
			
		||||
    'numeric'              => ':attribute musi być liczbą.',
 | 
			
		||||
    'regex'                => 'Format :attribute jest nieprawidłowy.',
 | 
			
		||||
    'required'             => 'Pole :attribute jest wymagane.',
 | 
			
		||||
    'required_if'          => 'Pole :attribute jest wymagane jeśli :other ma wartość :value.',
 | 
			
		||||
    'required_with'        => 'Pole :attribute jest wymagane jeśli :values zostało wprowadzone.',
 | 
			
		||||
    'required_with_all'    => 'Pole :attribute jest wymagane jeśli :values są obecne.',
 | 
			
		||||
    'required_without'     => 'Pole :attribute jest wymagane jeśli :values nie zostało wprowadzone.',
 | 
			
		||||
    'required_without_all' => 'Pole :attribute jest wymagane jeśli żadna z wartości :values nie została podana.',
 | 
			
		||||
    'same'                 => 'Pole :attribute i :other muszą być takie same.',
 | 
			
		||||
    'size'                 => [
 | 
			
		||||
        'numeric' => ':attribute musi mieć długość :size.',
 | 
			
		||||
        'file'    => ':attribute musi mieć :size kilobajtów.',
 | 
			
		||||
        'string'  => ':attribute mmusi mieć długość :size znaków.',
 | 
			
		||||
        'array'   => ':attribute musi posiadać :size elementów.',
 | 
			
		||||
    ],
 | 
			
		||||
    'string'               => ':attribute musi być ciągiem znaków.',
 | 
			
		||||
    'timezone'             => ':attribute musi być prawidłową strefą czasową.',
 | 
			
		||||
    'unique'               => ':attribute zostało już zajęte.',
 | 
			
		||||
    'url'                  => 'Format :attribute jest nieprawidłowy.',
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Custom Validation Language Lines
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | Here you may specify custom validation messages for attributes using the
 | 
			
		||||
    | convention "attribute.rule" to name the lines. This makes it quick to
 | 
			
		||||
    | specify a specific custom language line for a given attribute rule.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'custom' => [
 | 
			
		||||
        'password-confirm' => [
 | 
			
		||||
            'required_with' => 'Potwierdzenie hasła jest wymagane.',
 | 
			
		||||
        ],
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    | Custom Validation Attributes
 | 
			
		||||
    |--------------------------------------------------------------------------
 | 
			
		||||
    |
 | 
			
		||||
    | The following language lines are used to swap attribute place-holders
 | 
			
		||||
    | with something more reader friendly such as E-Mail Address instead
 | 
			
		||||
    | of "email". This simply helps us make messages a little cleaner.
 | 
			
		||||
    |
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'attributes' => [],
 | 
			
		||||
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +77,7 @@
 | 
			
		|||
        @yield('content')
 | 
			
		||||
    </section>
 | 
			
		||||
 | 
			
		||||
    <div id="back-to-top">
 | 
			
		||||
    <div back-to-top>
 | 
			
		||||
        <div class="inner">
 | 
			
		||||
            <i class="zmdi zmdi-chevron-up"></i> <span>{{ trans('common.back_to_top') }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,10 @@
 | 
			
		|||
<div class="book entity-list-item"  data-entity-type="book" data-entity-id="{{$book->id}}">
 | 
			
		||||
    <h4 class="text-book"><a class="text-book entity-list-item-link" href="{{$book->getUrl()}}"><i class="zmdi zmdi-book"></i><span class="entity-list-item-name">{{$book->name}}</span></a></h4>
 | 
			
		||||
    @if(isset($book->searchSnippet))
 | 
			
		||||
        <p class="text-muted">{!! $book->searchSnippet !!}</p>
 | 
			
		||||
    @else
 | 
			
		||||
        <p class="text-muted">{{ $book->getExcerpt() }}</p>
 | 
			
		||||
    @endif
 | 
			
		||||
    <div class="entity-item-snippet">
 | 
			
		||||
        @if(isset($book->searchSnippet))
 | 
			
		||||
            <p class="text-muted">{!! $book->searchSnippet !!}</p>
 | 
			
		||||
        @else
 | 
			
		||||
            <p class="text-muted">{{ $book->getExcerpt() }}</p>
 | 
			
		||||
        @endif
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -5,10 +5,10 @@
 | 
			
		|||
    <div class="faded-small toolbar">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-sm-6 faded">
 | 
			
		||||
                <div class="col-sm-6 col-xs-1  faded">
 | 
			
		||||
                    @include('books._breadcrumbs', ['book' => $book])
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-sm-6">
 | 
			
		||||
                <div class="col-sm-6 col-xs-11">
 | 
			
		||||
                    <div class="action-buttons faded">
 | 
			
		||||
                        <span dropdown class="dropdown-container">
 | 
			
		||||
                            <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.export') }}</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -50,13 +50,13 @@
 | 
			
		|||
    </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <div class="container" id="entity-dashboard" entity-id="{{ $book->id }}" entity-type="book">
 | 
			
		||||
    <div ng-non-bindable class="container" id="entity-dashboard" entity-id="{{ $book->id }}" entity-type="book">
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-md-7">
 | 
			
		||||
 | 
			
		||||
                <h1>{{$book->name}}</h1>
 | 
			
		||||
                <div class="book-content" v-if="!searching">
 | 
			
		||||
                    <p class="text-muted" v-pre>{{$book->description}}</p>
 | 
			
		||||
                    <p class="text-muted" v-pre>{!! nl2br(e($book->description)) !!}</p>
 | 
			
		||||
 | 
			
		||||
                    <div class="page-list" v-pre>
 | 
			
		||||
                        <hr>
 | 
			
		||||
| 
						 | 
				
			
			@ -72,9 +72,15 @@
 | 
			
		|||
                        @else
 | 
			
		||||
                            <p class="text-muted">{{ trans('entities.books_empty_contents') }}</p>
 | 
			
		||||
                            <p>
 | 
			
		||||
                                @if(userCan('page-create', $book))
 | 
			
		||||
                                <a href="{{ $book->getUrl('/page/create') }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ trans('entities.books_empty_create_page') }}</a>
 | 
			
		||||
                                @endif
 | 
			
		||||
                                @if(userCan('page-create', $book) && userCan('chapter-create', $book))
 | 
			
		||||
                                  <em class="text-muted">-{{ trans('entities.books_empty_or') }}-</em>   
 | 
			
		||||
                                @endif
 | 
			
		||||
                                @if(userCan('chapter-create', $book))
 | 
			
		||||
                                <a href="{{ $book->getUrl('/chapter/create') }}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>{{ trans('entities.books_empty_add_chapter') }}</a>
 | 
			
		||||
                                @endif
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <hr>
 | 
			
		||||
                        @endif
 | 
			
		||||
| 
						 | 
				
			
			@ -106,13 +112,13 @@
 | 
			
		|||
                @endif
 | 
			
		||||
 | 
			
		||||
                <div class="search-box">
 | 
			
		||||
                    <form v-on:submit="searchBook">
 | 
			
		||||
                    <form v-on:submit.prevent="searchBook">
 | 
			
		||||
                        <input v-model="searchTerm" v-on:change="checkSearchForm()" type="text" name="term" placeholder="{{ trans('entities.books_search_this') }}">
 | 
			
		||||
                        <button type="submit"><i class="zmdi zmdi-search"></i></button>
 | 
			
		||||
                        <button v-if="searching" v-cloak class="text-neg" v-on:click="clearSearch()" type="button"><i class="zmdi zmdi-close"></i></button>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                <div class="activity">
 | 
			
		||||
                    <h3>{{ trans('entities.recent_activity') }}</h3>
 | 
			
		||||
                    @include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)])
 | 
			
		||||
| 
						 | 
				
			
			@ -121,4 +127,4 @@
 | 
			
		|||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@stop
 | 
			
		||||
@stop
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
    <h4>
 | 
			
		||||
        @if (isset($showPath) && $showPath)
 | 
			
		||||
            <a href="{{ $chapter->book->getUrl() }}" class="text-book">
 | 
			
		||||
                <i class="zmdi zmdi-book"></i>{{ $chapter->book->name }}
 | 
			
		||||
                <i class="zmdi zmdi-book"></i>{{ $chapter->book->getShortName() }}
 | 
			
		||||
            </a>
 | 
			
		||||
            <span class="text-muted">  »  </span>
 | 
			
		||||
        @endif
 | 
			
		||||
| 
						 | 
				
			
			@ -10,14 +10,18 @@
 | 
			
		|||
            <i class="zmdi zmdi-collection-bookmark"></i><span class="entity-list-item-name">{{ $chapter->name }}</span>
 | 
			
		||||
        </a>
 | 
			
		||||
    </h4>
 | 
			
		||||
    @if(isset($chapter->searchSnippet))
 | 
			
		||||
        <p class="text-muted">{!! $chapter->searchSnippet !!}</p>
 | 
			
		||||
    @else
 | 
			
		||||
        <p class="text-muted">{{ $chapter->getExcerpt() }}</p>
 | 
			
		||||
    @endif
 | 
			
		||||
 | 
			
		||||
    <div class="entity-item-snippet">
 | 
			
		||||
        @if(isset($chapter->searchSnippet))
 | 
			
		||||
            <p class="text-muted">{!! $chapter->searchSnippet !!}</p>
 | 
			
		||||
        @else
 | 
			
		||||
            <p class="text-muted">{{ $chapter->getExcerpt() }}</p>
 | 
			
		||||
        @endif
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @if(!isset($hidePages) && count($chapter->pages) > 0)
 | 
			
		||||
        <p class="text-muted chapter-toggle"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ trans('entities.x_pages', ['count' => $chapter->pages->count()]) }}</span></p>
 | 
			
		||||
        <p chapter-toggle class="text-muted"><i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ trans('entities.x_pages', ['count' => $chapter->pages->count()]) }}</span></p>
 | 
			
		||||
        <div class="inset-list">
 | 
			
		||||
            @foreach($chapter->pages as $page)
 | 
			
		||||
                <h5 class="@if($page->draft) draft @endif"><a href="{{ $page->getUrl() }}" class="text-page @if($page->draft) draft @endif"><i class="zmdi zmdi-file-text"></i>{{$page->name}}</a></h5>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,10 +5,10 @@
 | 
			
		|||
    <div class="faded-small toolbar">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-sm-8 faded" ng-non-bindable>
 | 
			
		||||
                <div class="col-sm-6 col-xs-3 faded" ng-non-bindable>
 | 
			
		||||
                    @include('chapters._breadcrumbs', ['chapter' => $chapter])
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-sm-4 faded">
 | 
			
		||||
                <div class="col-sm-6 col-xs-9 faded">
 | 
			
		||||
                    <div class="action-buttons">
 | 
			
		||||
                        <span dropdown class="dropdown-container">
 | 
			
		||||
                            <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.export') }}</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -47,12 +47,12 @@
 | 
			
		|||
    </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <div class="container" id="entity-dashboard" entity-id="{{ $chapter->id }}" entity-type="chapter">
 | 
			
		||||
    <div class="container" id="entity-dashboard" ng-non-bindable entity-id="{{ $chapter->id }}" entity-type="chapter">
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-md-7">
 | 
			
		||||
                <h1>{{ $chapter->name }}</h1>
 | 
			
		||||
                <div class="chapter-content" v-if="!searching">
 | 
			
		||||
                    <p class="text-muted">{{ $chapter->description }}</p>
 | 
			
		||||
                    <p class="text-muted">{!! nl2br(e($chapter->description)) !!}</p>
 | 
			
		||||
 | 
			
		||||
                    @if(count($pages) > 0)
 | 
			
		||||
                        <div class="page-list">
 | 
			
		||||
| 
						 | 
				
			
			@ -116,7 +116,7 @@
 | 
			
		|||
                @endif
 | 
			
		||||
 | 
			
		||||
                <div class="search-box">
 | 
			
		||||
                    <form v-on:submit="searchBook">
 | 
			
		||||
                    <form v-on:submit.prevent="searchBook">
 | 
			
		||||
                        <input v-model="searchTerm" v-on:change="checkSearchForm()" type="text" name="term" placeholder="{{ trans('entities.chapters_search_this') }}">
 | 
			
		||||
                        <button type="submit"><i class="zmdi zmdi-search"></i></button>
 | 
			
		||||
                        <button v-if="searching" v-cloak class="text-neg" v-on:click="clearSearch()" type="button"><i class="zmdi zmdi-close"></i></button>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
<div id="code-editor">
 | 
			
		||||
    <div overlay ref="overlay" v-cloak @click="hide()">
 | 
			
		||||
        <div class="popup-body" @click.stop>
 | 
			
		||||
 | 
			
		||||
            <div class="popup-header primary-background">
 | 
			
		||||
                <div class="popup-title">{{ trans('components.code_editor') }}</div>
 | 
			
		||||
                <button class="overlay-close neg corner-button button" @click="hide()">x</button>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="padded">
 | 
			
		||||
                <div class="form-group">
 | 
			
		||||
                    <label for="code-editor-language">{{ trans('components.code_language') }}</label>
 | 
			
		||||
                    <div class="lang-options">
 | 
			
		||||
                        <small>
 | 
			
		||||
                            <a @click="updateLanguage('CSS')">CSS</a>
 | 
			
		||||
                            <a @click="updateLanguage('C')">C</a>
 | 
			
		||||
                            <a @click="updateLanguage('C++')">C++</a>
 | 
			
		||||
                            <a @click="updateLanguage('C#')">C#</a>
 | 
			
		||||
                            <a @click="updateLanguage('Go')">Go</a>
 | 
			
		||||
                            <a @click="updateLanguage('HTML')">HTML</a>
 | 
			
		||||
                            <a @click="updateLanguage('Java')">Java</a>
 | 
			
		||||
                            <a @click="updateLanguage('JavaScript')">JavaScript</a>
 | 
			
		||||
                            <a @click="updateLanguage('JSON')">JSON</a>
 | 
			
		||||
                            <a @click="updateLanguage('PHP')">PHP</a>
 | 
			
		||||
                            <a @click="updateLanguage('MarkDown')">MarkDown</a>
 | 
			
		||||
                            <a @click="updateLanguage('Nginx')">Nginx</a>
 | 
			
		||||
                            <a @click="updateLanguage('Python')">Python</a>
 | 
			
		||||
                            <a @click="updateLanguage('Ruby')">Ruby</a>
 | 
			
		||||
                            <a @click="updateLanguage('shell')">Shell/Bash</a>
 | 
			
		||||
                            <a @click="updateLanguage('SQL')">SQL</a>
 | 
			
		||||
                            <a @click="updateLanguage('XML')">XML</a>
 | 
			
		||||
                            <a @click="updateLanguage('YAML')">YAML</a>
 | 
			
		||||
                        </small>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <input @keypress.enter="save()" id="code-editor-language" type="text" @input="updateEditorMode(language)" v-model="language">
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="form-group">
 | 
			
		||||
                    <label for="code-editor-content">{{ trans('components.code_content') }}</label>
 | 
			
		||||
                    <textarea ref="editor" v-model="code"></textarea>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="form-group">
 | 
			
		||||
                    <button type="button" class="button pos" @click="save()">{{ trans('components.code_save') }}</button>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +1,9 @@
 | 
			
		|||
<div id="entity-selector-wrap">
 | 
			
		||||
    <div class="overlay" entity-link-selector>
 | 
			
		||||
    <div overlay entity-link-selector>
 | 
			
		||||
        <div class="popup-body small flex-child">
 | 
			
		||||
            <div class="popup-header primary-background">
 | 
			
		||||
                <div class="popup-title">{{ trans('entities.entity_select') }}</div>
 | 
			
		||||
                <button type="button" class="corner-button neg button popup-close">x</button>
 | 
			
		||||
                <button type="button" class="corner-button neg button overlay-close">x</button>
 | 
			
		||||
            </div>
 | 
			
		||||
            @include('components.entity-selector', ['name' => 'entity-selector'])
 | 
			
		||||
            <div class="popup-footer">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,91 +1,89 @@
 | 
			
		|||
<div id="image-manager" image-type="{{ $imageType }}" ng-controller="ImageManagerController" uploaded-to="{{ $uploaded_to or 0 }}">
 | 
			
		||||
    <div class="overlay" ng-cloak ng-click="hide()">
 | 
			
		||||
        <div class="popup-body" ng-click="$event.stopPropagation()">
 | 
			
		||||
<div id="image-manager" image-type="{{ $imageType }}" uploaded-to="{{ $uploaded_to or 0 }}">
 | 
			
		||||
    <div overlay v-cloak>
 | 
			
		||||
        <div class="popup-body" @click.stop="">
 | 
			
		||||
 | 
			
		||||
            <div class="popup-header primary-background">
 | 
			
		||||
                <div class="popup-title">{{ trans('components.image_select') }}</div>
 | 
			
		||||
                <button class="popup-close neg corner-button button">x</button>
 | 
			
		||||
                <button class="overlay-close neg corner-button button">x</button>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="flex-fill image-manager-body">
 | 
			
		||||
 | 
			
		||||
                <div class="image-manager-content">
 | 
			
		||||
                    <div ng-if="imageType === 'gallery'" class="container">
 | 
			
		||||
                    <div v-if="imageType === 'gallery'" class="container">
 | 
			
		||||
                        <div class="image-manager-header row faded-small nav-tabs">
 | 
			
		||||
                            <div class="col-xs-4 tab-item" title="{{ trans('components.image_all_title') }}" ng-class="{selected: (view=='all')}" ng-click="setView('all')"><i class="zmdi zmdi-collection-image"></i> {{ trans('components.image_all') }}</div>
 | 
			
		||||
                            <div class="col-xs-4 tab-item" title="{{ trans('components.image_book_title') }}" ng-class="{selected: (view=='book')}" ng-click="setView('book')"><i class="zmdi zmdi-book text-book"></i> {{ trans('entities.book') }}</div>
 | 
			
		||||
                            <div class="col-xs-4 tab-item" title="{{ trans('components.image_page_title') }}" ng-class="{selected: (view=='page')}" ng-click="setView('page')"><i class="zmdi zmdi-file-text text-page"></i> {{ trans('entities.page') }}</div>
 | 
			
		||||
                            <div class="col-xs-4 tab-item" title="{{ trans('components.image_all_title') }}" :class="{selected: (view=='all')}" @click="setView('all')"><i class="zmdi zmdi-collection-image"></i> {{ trans('components.image_all') }}</div>
 | 
			
		||||
                            <div class="col-xs-4 tab-item" title="{{ trans('components.image_book_title') }}" :class="{selected: (view=='book')}" @click="setView('book')"><i class="zmdi zmdi-book text-book"></i> {{ trans('entities.book') }}</div>
 | 
			
		||||
                            <div class="col-xs-4 tab-item" title="{{ trans('components.image_page_title') }}" :class="{selected: (view=='page')}" @click="setView('page')"><i class="zmdi zmdi-file-text text-page"></i> {{ trans('entities.page') }}</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div ng-show="view === 'all'" >
 | 
			
		||||
                        <form ng-submit="searchImages()" class="contained-search-box">
 | 
			
		||||
                            <input type="text" placeholder="{{ trans('components.image_search_hint') }}" ng-model="searchTerm">
 | 
			
		||||
                            <button ng-class="{active: searching}" title="{{ trans('common.search_clear') }}" type="button" ng-click="cancelSearch()" class="text-button cancel"><i class="zmdi zmdi-close-circle-o"></i></button>
 | 
			
		||||
                            <button title="{{ trans('common.search') }}" class="text-button" type="submit"><i class="zmdi zmdi-search"></i></button>
 | 
			
		||||
                    <div v-show="view === 'all'" >
 | 
			
		||||
                        <form @submit="searchImages" class="contained-search-box">
 | 
			
		||||
                            <input placeholder="{{ trans('components.image_search_hint') }}" v-model="searchTerm">
 | 
			
		||||
                            <button :class="{active: searching}" title="{{ trans('common.search_clear') }}" type="button" @click="cancelSearch()" class="text-button cancel"><i class="zmdi zmdi-close-circle-o"></i></button>
 | 
			
		||||
                            <button title="{{ trans('common.search') }}" class="text-button"><i class="zmdi zmdi-search"></i></button>
 | 
			
		||||
                        </form>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="image-manager-list">
 | 
			
		||||
                        <div ng-repeat="image in images">
 | 
			
		||||
                            <div class="image anim fadeIn" ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}"
 | 
			
		||||
                                 ng-class="{selected: (image==selectedImage)}" ng-click="imageSelect(image)">
 | 
			
		||||
                                <img ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}">
 | 
			
		||||
                        <div v-if="images.length > 0" v-for="(image, idx) in images">
 | 
			
		||||
                            <div class="image anim fadeIn" :style="{animationDelay: (idx > 26) ? '160ms' : ((idx * 25) + 'ms')}"
 | 
			
		||||
                                 :class="{selected: (image==selectedImage)}" @click="imageSelect(image)">
 | 
			
		||||
                                <img :src="image.thumbs.gallery" :alt="image.title" :title="image.name">
 | 
			
		||||
                                <div class="image-meta">
 | 
			
		||||
                                    <span class="name" ng-bind="image.name"></span>
 | 
			
		||||
                                    <span class="name" v-text="image.name"></span>
 | 
			
		||||
                                    <span class="date">{{ trans('components.image_uploaded', ['uploadedDate' => "{{ getDate(image.created_at) }" . "}"]) }}</span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="load-more" ng-show="hasMore" ng-click="fetchData()">{{ trans('components.image_load_more') }}</div>
 | 
			
		||||
                        <div class="load-more" v-show="hasMore" @click="fetchData">{{ trans('components.image_load_more') }}</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="image-manager-sidebar">
 | 
			
		||||
                    <div class="inner">
 | 
			
		||||
 | 
			
		||||
                        <div class="image-manager-details anim fadeIn" ng-show="selectedImage">
 | 
			
		||||
                        <div class="image-manager-details anim fadeIn" v-if="selectedImage">
 | 
			
		||||
 | 
			
		||||
                            <form ng-submit="saveImageDetails($event)">
 | 
			
		||||
                            <form @submit.prevent="saveImageDetails">
 | 
			
		||||
                                <div>
 | 
			
		||||
                                    <a ng-href="@{{selectedImage.url}}" target="_blank" style="display: block;">
 | 
			
		||||
                                        <img ng-src="@{{selectedImage.thumbs.gallery}}" ng-attr-alt="@{{selectedImage.title}}" ng-attr-title="@{{selectedImage.name}}">
 | 
			
		||||
                                    <a :href="selectedImage.url" target="_blank" style="display: block;">
 | 
			
		||||
                                        <img :src="selectedImage.thumbs.gallery" :alt="selectedImage.title"
 | 
			
		||||
                                             :title="selectedImage.name">
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-group">
 | 
			
		||||
                                    <label for="name">{{ trans('components.image_image_name') }}</label>
 | 
			
		||||
                                    <input type="text" id="name" name="name" ng-model="selectedImage.name">
 | 
			
		||||
                                    <input id="name" name="name" v-model="selectedImage.name">
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </form>
 | 
			
		||||
 | 
			
		||||
                            <div ng-show="dependantPages">
 | 
			
		||||
                            <div v-show="dependantPages">
 | 
			
		||||
                                <p class="text-neg text-small">
 | 
			
		||||
                                    {{ trans('components.image_delete_confirm') }}
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <ul class="text-neg">
 | 
			
		||||
                                    <li ng-repeat="page in dependantPages">
 | 
			
		||||
                                        <a ng-href="@{{ page.url }}" target="_blank" class="text-neg" ng-bind="page.name"></a>
 | 
			
		||||
                                    <li v-for="page in dependantPages">
 | 
			
		||||
                                        <a :href="page.url" target="_blank" class="text-neg" v-text="page.name"></a>
 | 
			
		||||
                                    </li>
 | 
			
		||||
                                </ul>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="clearfix">
 | 
			
		||||
                                <form class="float left" ng-submit="deleteImage($event)">
 | 
			
		||||
                                <form class="float left" @submit.prevent="deleteImage">
 | 
			
		||||
                                    <button class="button icon neg"><i class="zmdi zmdi-delete"></i></button>
 | 
			
		||||
                                </form>
 | 
			
		||||
                                <button class="button pos anim fadeIn float right" ng-show="selectedImage" ng-click="selectButtonClick()">
 | 
			
		||||
                                <button class="button pos anim fadeIn float right" v-show="selectedImage" @click="callbackAndHide(selectedImage)">
 | 
			
		||||
                                    <i class="zmdi zmdi-square-right"></i>{{ trans('components.image_select_image') }}
 | 
			
		||||
                                </button>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <drop-zone message="{{ trans('components.image_dropzone') }}" upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
 | 
			
		||||
 | 
			
		||||
                        <dropzone placeholder="{{ trans('components.image_dropzone') }}" :upload-url="uploadUrl" :uploaded-to="uploadedTo" @success="uploadSuccess"></dropzone>
 | 
			
		||||
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@
 | 
			
		|||
            <div class="row">
 | 
			
		||||
                <div class="col-sm-6 faded">
 | 
			
		||||
                    <div class="action-buttons text-left">
 | 
			
		||||
                        <a data-action="expand-entity-list-details" class="text-primary text-button"><i class="zmdi zmdi-wrap-text"></i>{{ trans('common.toggle_details') }}</a>
 | 
			
		||||
                        <a expand-toggle=".entity-list.compact .entity-item-snippet" class="text-primary text-button"><i class="zmdi zmdi-wrap-text"></i>{{ trans('common.toggle_details') }}</a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@
 | 
			
		|||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    @include('components.image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
 | 
			
		||||
    @include('components.code-editor')
 | 
			
		||||
    @include('components.entity-selector-popup')
 | 
			
		||||
 | 
			
		||||
@stop
 | 
			
		||||
| 
						 | 
				
			
			@ -9,31 +9,40 @@
 | 
			
		|||
        @endif
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div toolbox-tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
 | 
			
		||||
    <div toolbox-tab-content="tags" id="tag-manager" page-id="{{ $page->id or 0 }}">
 | 
			
		||||
        <h4>{{ trans('entities.page_tags') }}</h4>
 | 
			
		||||
        <div class="padded tags">
 | 
			
		||||
            <p class="muted small">{!! nl2br(e(trans('entities.tags_explain'))) !!}</p>
 | 
			
		||||
            <table class="no-style" tag-autosuggestions style="width: 100%;">
 | 
			
		||||
                <tbody ui-sortable="sortOptions" ng-model="tags" >
 | 
			
		||||
                    <tr ng-repeat="tag in tags track by $index">
 | 
			
		||||
                        <td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
 | 
			
		||||
                        <td><input autosuggest="{{ baseUrl('/ajax/tags/suggest/names') }}" autosuggest-type="name" class="outline" ng-attr-name="tags[@{{$index}}][name]" type="text" ng-model="tag.name" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="{{ trans('entities.tag') }}"></td>
 | 
			
		||||
                        <td><input autosuggest="{{ baseUrl('/ajax/tags/suggest/values') }}" autosuggest-type="value" class="outline" ng-attr-name="tags[@{{$index}}][value]" type="text" ng-model="tag.value" ng-change="tagChange(tag)" ng-blur="tagBlur(tag)" placeholder="{{ trans('entities.tag_value') }}"></td>
 | 
			
		||||
                        <td width="10" ng-show="tags.length != 1" class="text-center text-neg" style="padding: 0;" ng-click="removeTag(tag)"><i class="zmdi zmdi-close"></i></td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
 | 
			
		||||
            <draggable class="fake-table no-style tag-table" :options="{handle: '.handle'}" :list="tags" element="div" style="width: 100%;">
 | 
			
		||||
                <transition-group name="test" tag="div">
 | 
			
		||||
                    <div v-for="(tag, i) in tags" :key="tag.key">
 | 
			
		||||
                        <div width="20" class="handle" ><i class="zmdi zmdi-menu"></i></div>
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <autosuggest url="/ajax/tags/suggest/names" type="name" class="outline" :name="getTagFieldName(i, 'name')"
 | 
			
		||||
                                   v-model="tag.name" @input="tagChange(tag)" @blur="tagBlur(tag)" placeholder="{{ trans('entities.tag') }}"/>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <autosuggest url="/ajax/tags/suggest/values" type="value" class="outline" :name="getTagFieldName(i, 'value')"
 | 
			
		||||
                                         v-model="tag.value" @change="tagChange(tag)" @blur="tagBlur(tag)" placeholder="{{ trans('entities.tag') }}"/>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div width="10" v-show="tags.length !== 1" class="text-center text-neg" style="padding: 0;" @click="removeTag(tag)"><i class="zmdi zmdi-close"></i></div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </transition-group>
 | 
			
		||||
            </draggable>
 | 
			
		||||
 | 
			
		||||
            <table class="no-style" style="width: 100%;">
 | 
			
		||||
                <tbody>
 | 
			
		||||
                <tr class="unsortable">
 | 
			
		||||
                    <td  width="34"></td>
 | 
			
		||||
                    <td ng-click="addEmptyTag()">
 | 
			
		||||
                    <td width="34"></td>
 | 
			
		||||
                    <td @click="addEmptyTag">
 | 
			
		||||
                        <button type="button" class="text-button">{{ trans('entities.tags_add') }}</button>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -81,15 +90,15 @@
 | 
			
		|||
                            <p class="muted small">{{ trans('entities.attachments_explain_link') }}</p>
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="attachment-via-link">{{ trans('entities.attachments_link_name') }}</label>
 | 
			
		||||
                                <input type="text" placeholder="{{ trans('entities.attachments_link_name') }}" ng-model="file.name">
 | 
			
		||||
                                <input placeholder="{{ trans('entities.attachments_link_name') }}" ng-model="file.name">
 | 
			
		||||
                                <p class="small neg" ng-repeat="error in errors.link.name" ng-bind="error"></p>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="attachment-via-link">{{ trans('entities.attachments_link_url') }}</label>
 | 
			
		||||
                                <input type="text" placeholder="{{ trans('entities.attachments_link_url_hint') }}" ng-model="file.link">
 | 
			
		||||
                                <input placeholder="{{ trans('entities.attachments_link_url_hint') }}" ng-model="file.link">
 | 
			
		||||
                                <p class="small neg" ng-repeat="error in errors.link.link" ng-bind="error"></p>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <button type="submit" class="button pos">{{ trans('entities.attach') }}</button>
 | 
			
		||||
                            <button class="button pos">{{ trans('entities.attach') }}</button>
 | 
			
		||||
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -117,14 +126,14 @@
 | 
			
		|||
                        <div tab-content="link">
 | 
			
		||||
                            <div class="form-group">
 | 
			
		||||
                                <label for="attachment-link-edit">{{ trans('entities.attachments_link_url') }}</label>
 | 
			
		||||
                                <input type="text" id="attachment-link-edit" placeholder="{{ trans('entities.attachment_link') }}" ng-model="editFile.link">
 | 
			
		||||
                                <input id="attachment-link-edit" placeholder="{{ trans('entities.attachment_link') }}" ng-model="editFile.link">
 | 
			
		||||
                                <p class="small neg" ng-repeat="error in errors.edit.link" ng-bind="error"></p>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <button type="button" class="button" ng-click="cancelEdit()">{{ trans('common.back') }}</button>
 | 
			
		||||
                    <button type="submit" class="button pos">{{ trans('common.save') }}</button>
 | 
			
		||||
                    <button class="button pos">{{ trans('common.save') }}</button>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,27 @@
 | 
			
		|||
<div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}">
 | 
			
		||||
    <h4>
 | 
			
		||||
        @if (isset($showPath) && $showPath)
 | 
			
		||||
            <a href="{{ $page->book->getUrl() }}" class="text-book">
 | 
			
		||||
                <i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}
 | 
			
		||||
            </a>
 | 
			
		||||
            <span class="text-muted">  »  </span>
 | 
			
		||||
            @if($page->chapter)
 | 
			
		||||
                <a href="{{ $page->chapter->getUrl() }}" class="text-chapter">
 | 
			
		||||
                    <i class="zmdi zmdi-collection-bookmark"></i>{{ $page->chapter->getShortName() }}
 | 
			
		||||
                </a>
 | 
			
		||||
                <span class="text-muted">  »  </span>
 | 
			
		||||
            @endif
 | 
			
		||||
        @endif
 | 
			
		||||
        <a href="{{ $page->getUrl() }}" class="text-page entity-list-item-link"><i class="zmdi zmdi-file-text"></i><span class="entity-list-item-name">{{ $page->name }}</span></a>
 | 
			
		||||
    </h4>
 | 
			
		||||
 | 
			
		||||
    @if(isset($page->searchSnippet))
 | 
			
		||||
        <p class="text-muted">{!! $page->searchSnippet !!}</p>
 | 
			
		||||
    @else
 | 
			
		||||
        <p class="text-muted">{{ $page->getExcerpt() }}</p>
 | 
			
		||||
    @endif
 | 
			
		||||
    <div class="entity-item-snippet">
 | 
			
		||||
        @if(isset($page->searchSnippet))
 | 
			
		||||
            <p class="text-muted">{!! $page->searchSnippet !!}</p>
 | 
			
		||||
        @else
 | 
			
		||||
            <p class="text-muted">{{ $page->getExcerpt() }}</p>
 | 
			
		||||
        @endif
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    @if(isset($style) && $style === 'detailed')
 | 
			
		||||
        <div class="row meta text-muted text-small">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,10 +5,10 @@
 | 
			
		|||
    <div class="faded-small toolbar">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-sm-6 faded">
 | 
			
		||||
                <div class="col-sm-8 col-xs-5 faded">
 | 
			
		||||
                    @include('pages._breadcrumbs', ['page' => $page])
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-sm-6 faded">
 | 
			
		||||
                <div class="col-sm-4 col-xs-7 faded">
 | 
			
		||||
                    <div class="action-buttons">
 | 
			
		||||
                        <span dropdown class="dropdown-container">
 | 
			
		||||
                            <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.export') }}</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,7 +51,7 @@
 | 
			
		|||
                </a>
 | 
			
		||||
 | 
			
		||||
                @if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
 | 
			
		||||
                    <p class="text-muted chapter-toggle @if($bookChild->matchesOrContains($current)) open @endif">
 | 
			
		||||
                    <p chapter-toggle class="text-muted @if($bookChild->matchesOrContains($current)) open @endif">
 | 
			
		||||
                        <i class="zmdi zmdi-caret-right"></i> <i class="zmdi zmdi-file-text"></i> <span>{{ trans('entities.x_pages', ['count' => $bookChild->pages->count()]) }}</span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <ul class="menu sub-menu inset-list @if($bookChild->matchesOrContains($current)) open @endif">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<style id="custom-styles" data-color="{{ setting('app-color') }}" data-color-light="{{ setting('app-color-light') }}">
 | 
			
		||||
    header, #back-to-top, .primary-background {
 | 
			
		||||
    header, [back-to-top], .primary-background {
 | 
			
		||||
        background-color: {{ setting('app-color') }} !important;
 | 
			
		||||
    }
 | 
			
		||||
    .faded-small, .primary-background-light {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,12 @@
 | 
			
		|||
 | 
			
		||||
<div class="notification anim pos" @if(!session()->has('success')) style="display:none;" @endif>
 | 
			
		||||
<div notification="success" data-autohide class="pos" @if(session()->has('success')) data-show @endif>
 | 
			
		||||
    <i class="zmdi zmdi-check-circle"></i> <span>{!! nl2br(htmlentities(session()->get('success'))) !!}</span>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="notification anim warning stopped" @if(!session()->has('warning')) style="display:none;" @endif>
 | 
			
		||||
<div notification="warning" class="warning" @if(session()->has('warning')) data-show @endif>
 | 
			
		||||
    <i class="zmdi zmdi-info"></i> <span>{!! nl2br(htmlentities(session()->get('warning'))) !!}</span>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="notification anim neg stopped" @if(!session()->has('error')) style="display:none;" @endif>
 | 
			
		||||
<div notification="error" class="neg" @if(session()->has('error')) data-show @endif>
 | 
			
		||||
    <i class="zmdi zmdi-alert-circle"></i> <span>{!! nl2br(htmlentities(session()->get('error'))) !!}</span>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
    @if(count($entities) > 0)
 | 
			
		||||
        @foreach($entities as $index => $entity)
 | 
			
		||||
            @if($entity->isA('page'))
 | 
			
		||||
                @include('pages/list-item', ['page' => $entity])
 | 
			
		||||
                @include('pages/list-item', ['page' => $entity, 'showPath' => true])
 | 
			
		||||
            @elseif($entity->isA('book'))
 | 
			
		||||
                @include('books/list-item', ['book' => $entity])
 | 
			
		||||
            @elseif($entity->isA('chapter'))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ class LdapTest extends BrowserKitTest
 | 
			
		|||
    public function setUp()
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
        if (!defined('LDAP_OPT_REFERRALS')) define('LDAP_OPT_REFERRALS', 1);
 | 
			
		||||
        app('config')->set(['auth.method' => 'ldap', 'services.ldap.base_dn' => 'dc=ldap,dc=local', 'auth.providers.users.driver' => 'ldap']);
 | 
			
		||||
        $this->mockLdap = \Mockery::mock(\BookStack\Services\Ldap::class);
 | 
			
		||||
        $this->app['BookStack\Services\Ldap'] = $this->mockLdap;
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +22,7 @@ class LdapTest extends BrowserKitTest
 | 
			
		|||
    {
 | 
			
		||||
        $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
 | 
			
		||||
        $this->mockLdap->shouldReceive('setVersion')->once();
 | 
			
		||||
        $this->mockLdap->shouldReceive('setOption')->times(4);
 | 
			
		||||
        $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
 | 
			
		||||
            ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
 | 
			
		||||
            ->andReturn(['count' => 1, 0 => [
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +51,7 @@ class LdapTest extends BrowserKitTest
 | 
			
		|||
        $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
 | 
			
		||||
        $this->mockLdap->shouldReceive('setVersion')->once();
 | 
			
		||||
        $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
 | 
			
		||||
        $this->mockLdap->shouldReceive('setOption')->times(2);
 | 
			
		||||
        $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
 | 
			
		||||
            ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
 | 
			
		||||
            ->andReturn(['count' => 1, 0 => [
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +75,7 @@ class LdapTest extends BrowserKitTest
 | 
			
		|||
    {
 | 
			
		||||
        $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
 | 
			
		||||
        $this->mockLdap->shouldReceive('setVersion')->once();
 | 
			
		||||
        $this->mockLdap->shouldReceive('setOption')->times(2);
 | 
			
		||||
        $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
 | 
			
		||||
            ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
 | 
			
		||||
            ->andReturn(['count' => 1, 0 => [
 | 
			
		||||
| 
						 | 
				
			
			@ -129,4 +133,4 @@ class LdapTest extends BrowserKitTest
 | 
			
		|||
            ->dontSee('External Authentication');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
<?php namespace Tests;
 | 
			
		||||
 | 
			
		||||
use BookStack\Entity;
 | 
			
		||||
use BookStack\Role;
 | 
			
		||||
use BookStack\Services\PermissionService;
 | 
			
		||||
use Illuminate\Contracts\Console\Kernel;
 | 
			
		||||
| 
						 | 
				
			
			@ -117,6 +118,16 @@ abstract class BrowserKitTest extends TestCase
 | 
			
		|||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper for updating entity permissions.
 | 
			
		||||
     * @param Entity $entity
 | 
			
		||||
     */
 | 
			
		||||
    protected function updateEntityPermissions(Entity $entity)
 | 
			
		||||
    {
 | 
			
		||||
        $restrictionService = $this->app[PermissionService::class];
 | 
			
		||||
        $restrictionService->buildJointPermissionsForEntity($entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Quick way to create a new user
 | 
			
		||||
     * @param array $attributes
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
<?php namespace Tests;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
use BookStack\Chapter;
 | 
			
		||||
use BookStack\Page;
 | 
			
		||||
 | 
			
		||||
class EntitySearchTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -75,10 +78,10 @@ class EntitySearchTest extends TestCase
 | 
			
		|||
            ])
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        $pageA = \BookStack\Page::first();
 | 
			
		||||
        $pageA = Page::first();
 | 
			
		||||
        $pageA->tags()->saveMany($newTags);
 | 
			
		||||
 | 
			
		||||
        $pageB = \BookStack\Page::all()->last();
 | 
			
		||||
        $pageB = Page::all()->last();
 | 
			
		||||
        $pageB->tags()->create(['name' => 'animal', 'value' => 'dog']);
 | 
			
		||||
 | 
			
		||||
        $this->asEditor();
 | 
			
		||||
| 
						 | 
				
			
			@ -160,8 +163,8 @@ class EntitySearchTest extends TestCase
 | 
			
		|||
 | 
			
		||||
    public function test_ajax_entity_search()
 | 
			
		||||
    {
 | 
			
		||||
        $page = \BookStack\Page::all()->last();
 | 
			
		||||
        $notVisitedPage = \BookStack\Page::first();
 | 
			
		||||
        $page = Page::all()->last();
 | 
			
		||||
        $notVisitedPage = Page::first();
 | 
			
		||||
 | 
			
		||||
        // Visit the page to make popular
 | 
			
		||||
        $this->asEditor()->get($page->getUrl());
 | 
			
		||||
| 
						 | 
				
			
			@ -176,4 +179,20 @@ class EntitySearchTest extends TestCase
 | 
			
		|||
        $defaultListTest->assertSee($page->name);
 | 
			
		||||
        $defaultListTest->assertDontSee($notVisitedPage->name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function test_ajax_entity_serach_shows_breadcrumbs()
 | 
			
		||||
    {
 | 
			
		||||
        $chapter = Chapter::first();
 | 
			
		||||
        $page = $chapter->pages->first();
 | 
			
		||||
        $this->asEditor();
 | 
			
		||||
 | 
			
		||||
        $pageSearch = $this->get('/ajax/search/entities?term=' . urlencode($page->name));
 | 
			
		||||
        $pageSearch->assertSee($page->name);
 | 
			
		||||
        $pageSearch->assertSee($chapter->getShortName());
 | 
			
		||||
        $pageSearch->assertSee($page->book->getShortName());
 | 
			
		||||
 | 
			
		||||
        $chapterSearch = $this->get('/ajax/search/entities?term=' . urlencode($chapter->name));
 | 
			
		||||
        $chapterSearch->assertSee($chapter->name);
 | 
			
		||||
        $chapterSearch->assertSee($chapter->book->getShortName());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,10 @@
 | 
			
		|||
<?php namespace Tests;
 | 
			
		||||
 | 
			
		||||
use BookStack\Page;
 | 
			
		||||
use BookStack\Repos\PermissionsRepo;
 | 
			
		||||
use BookStack\Role;
 | 
			
		||||
use Laravel\BrowserKitTesting\HttpException;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 | 
			
		||||
 | 
			
		||||
class RolesTest extends BrowserKitTest
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -580,8 +583,6 @@ class RolesTest extends BrowserKitTest
 | 
			
		|||
            ->see('Cannot be deleted');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function test_image_delete_own_permission()
 | 
			
		||||
    {
 | 
			
		||||
        $this->giveUserPermissions($this->user, ['image-update-all']);
 | 
			
		||||
| 
						 | 
				
			
			@ -620,6 +621,42 @@ class RolesTest extends BrowserKitTest
 | 
			
		|||
            ->dontSeeInDatabase('images', ['id' => $image->id]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function test_role_permission_removal()
 | 
			
		||||
    {
 | 
			
		||||
        // To cover issue fixed in f99c8ff99aee9beb8c692f36d4b84dc6e651e50a.
 | 
			
		||||
        $page = Page::first();
 | 
			
		||||
        $viewerRole = \BookStack\Role::getRole('viewer');
 | 
			
		||||
        $viewer = $this->getViewer();
 | 
			
		||||
        $this->actingAs($viewer)->visit($page->getUrl())->assertResponseOk();
 | 
			
		||||
 | 
			
		||||
        $this->asAdmin()->put('/settings/roles/' . $viewerRole->id, [
 | 
			
		||||
            'display_name' => $viewerRole->display_name,
 | 
			
		||||
            'description' => $viewerRole->description,
 | 
			
		||||
            'permission' => []
 | 
			
		||||
        ])->assertResponseStatus(302);
 | 
			
		||||
 | 
			
		||||
        $this->expectException(HttpException::class);
 | 
			
		||||
        $this->actingAs($viewer)->visit($page->getUrl())->assertResponseStatus(404);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function test_empty_state_actions_not_visible_without_permission()
 | 
			
		||||
    {
 | 
			
		||||
        $admin = $this->getAdmin();
 | 
			
		||||
        // Book links
 | 
			
		||||
        $book = factory(\BookStack\Book::class)->create(['created_by' => $admin->id, 'updated_by' => $admin->id]);
 | 
			
		||||
        $this->updateEntityPermissions($book);
 | 
			
		||||
        $this->actingAs($this->getViewer())->visit($book->getUrl())
 | 
			
		||||
            ->dontSee('Create a new page')
 | 
			
		||||
            ->dontSee('Add a chapter');
 | 
			
		||||
 | 
			
		||||
        // Chapter links
 | 
			
		||||
        $chapter = factory(\BookStack\Chapter::class)->create(['created_by' => $admin->id, 'updated_by' => $admin->id, 'book_id' => $book->id]);
 | 
			
		||||
        $this->updateEntityPermissions($chapter);
 | 
			
		||||
        $this->actingAs($this->getViewer())->visit($chapter->getUrl())
 | 
			
		||||
            ->dontSee('Create a new page')
 | 
			
		||||
            ->dontSee('Sort the current book');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function test_comment_create_permission () {
 | 
			
		||||
        $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue