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
|
- cat storage/logs/laravel.log
|
||||||
|
|
||||||
script:
|
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\ClearActivity::class,
|
||||||
Commands\ClearRevisions::class,
|
Commands\ClearRevisions::class,
|
||||||
Commands\RegeneratePermissions::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) {
|
\Blade::directive('icon', function($expression) {
|
||||||
return "<?php echo icon($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->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id);
|
||||||
$draftPage->html = $this->formatHtml($input['html']);
|
$draftPage->html = $this->formatHtml($input['html']);
|
||||||
$draftPage->text = strip_tags($draftPage->html);
|
$draftPage->text = $this->pageToPlainText($draftPage);
|
||||||
$draftPage->draft = false;
|
$draftPage->draft = false;
|
||||||
$draftPage->revision_count = 1;
|
$draftPage->revision_count = 1;
|
||||||
|
|
||||||
|
@ -713,6 +713,17 @@ class EntityRepo
|
||||||
return $content;
|
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.
|
* Get a new draft page instance.
|
||||||
* @param Book $book
|
* @param Book $book
|
||||||
|
@ -816,7 +827,7 @@ class EntityRepo
|
||||||
$userId = user()->id;
|
$userId = user()->id;
|
||||||
$page->fill($input);
|
$page->fill($input);
|
||||||
$page->html = $this->formatHtml($input['html']);
|
$page->html = $this->formatHtml($input['html']);
|
||||||
$page->text = strip_tags($page->html);
|
$page->text = $this->pageToPlainText($page);
|
||||||
if (setting('app-editor') !== 'markdown') $page->markdown = '';
|
if (setting('app-editor') !== 'markdown') $page->markdown = '';
|
||||||
$page->updated_by = $userId;
|
$page->updated_by = $userId;
|
||||||
$page->revision_count++;
|
$page->revision_count++;
|
||||||
|
@ -933,7 +944,7 @@ class EntityRepo
|
||||||
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
|
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
|
||||||
$page->fill($revision->toArray());
|
$page->fill($revision->toArray());
|
||||||
$page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
|
$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->updated_by = user()->id;
|
||||||
$page->save();
|
$page->save();
|
||||||
$this->searchService->indexEntity($page);
|
$this->searchService->indexEntity($page);
|
||||||
|
@ -953,7 +964,7 @@ class EntityRepo
|
||||||
if ($page->draft) {
|
if ($page->draft) {
|
||||||
$page->fill($data);
|
$page->fill($data);
|
||||||
if (isset($data['html'])) {
|
if (isset($data['html'])) {
|
||||||
$page->text = strip_tags($data['html']);
|
$page->text = $this->pageToPlainText($page);
|
||||||
}
|
}
|
||||||
$page->save();
|
$page->save();
|
||||||
return $page;
|
return $page;
|
||||||
|
|
|
@ -42,6 +42,8 @@ class LdapService
|
||||||
$userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
|
$userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
|
||||||
$baseDn = $this->config['base_dn'];
|
$baseDn = $this->config['base_dn'];
|
||||||
$emailAttr = $this->config['email_attribute'];
|
$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]);
|
$users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', $emailAttr]);
|
||||||
if ($users['count'] === 0) return null;
|
if ($users['count'] === 0) return null;
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,7 @@ class PermissionService
|
||||||
$roleIds = array_map(function($role) {
|
$roleIds = array_map(function($role) {
|
||||||
return $role->id;
|
return $role->id;
|
||||||
}, $roles);
|
}, $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'),
|
'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 [
|
return [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -70,12 +78,13 @@ return [
|
||||||
|
|
||||||
'mysql' => [
|
'mysql' => [
|
||||||
'driver' => 'mysql',
|
'driver' => 'mysql',
|
||||||
'host' => env('DB_HOST', 'localhost'),
|
'host' => $mysql_host,
|
||||||
'database' => env('DB_DATABASE', 'forge'),
|
'database' => env('DB_DATABASE', 'forge'),
|
||||||
'username' => env('DB_USERNAME', 'forge'),
|
'username' => env('DB_USERNAME', 'forge'),
|
||||||
'password' => env('DB_PASSWORD', ''),
|
'password' => env('DB_PASSWORD', ''),
|
||||||
'charset' => 'utf8',
|
'port' => $mysql_port,
|
||||||
'collation' => 'utf8_unicode_ci',
|
'charset' => 'utf8mb4',
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
'prefix' => '',
|
'prefix' => '',
|
||||||
'strict' => false,
|
'strict' => false,
|
||||||
],
|
],
|
||||||
|
|
|
@ -80,6 +80,7 @@ return [
|
||||||
'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
|
'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
|
||||||
'version' => env('LDAP_VERSION', false),
|
'version' => env('LDAP_VERSION', false),
|
||||||
'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
|
'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 watchify = require("watchify");
|
||||||
const envify = require("envify");
|
const envify = require("envify");
|
||||||
const gutil = require("gulp-util");
|
const gutil = require("gulp-util");
|
||||||
|
const liveReload = require('gulp-livereload');
|
||||||
|
|
||||||
if (argv.production) process.env.NODE_ENV = 'production';
|
if (argv.production) process.env.NODE_ENV = 'production';
|
||||||
|
let isProduction = argv.production || process.env.NODE_ENV === 'production';
|
||||||
|
|
||||||
gulp.task('styles', () => {
|
gulp.task('styles', () => {
|
||||||
let chain = gulp.src(['resources/assets/sass/**/*.scss'])
|
let chain = gulp.src(['resources/assets/sass/**/*.scss'])
|
||||||
|
@ -26,31 +28,40 @@ gulp.task('styles', () => {
|
||||||
}}))
|
}}))
|
||||||
.pipe(sass())
|
.pipe(sass())
|
||||||
.pipe(autoprefixer('last 2 versions'));
|
.pipe(autoprefixer('last 2 versions'));
|
||||||
if (argv.production) chain = chain.pipe(minifycss());
|
if (isProduction) chain = chain.pipe(minifycss());
|
||||||
return chain.pipe(gulp.dest('public/css/'));
|
return chain.pipe(gulp.dest('public/css/')).pipe(liveReload());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function scriptTask(watch=false) {
|
function scriptTask(watch = false) {
|
||||||
|
|
||||||
let props = {
|
let props = {
|
||||||
basedir: 'resources/assets/js',
|
basedir: 'resources/assets/js',
|
||||||
debug: true,
|
debug: true,
|
||||||
entries: ['global.js']
|
entries: ['global.js'],
|
||||||
|
fast: !isProduction,
|
||||||
|
cache: {},
|
||||||
|
packageCache: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
let bundler = watch ? watchify(browserify(props), { poll: true }) : browserify(props);
|
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() {
|
function rebundle() {
|
||||||
let stream = bundler.bundle();
|
let stream = bundler.bundle();
|
||||||
stream = stream.pipe(source('common.js'));
|
stream = stream.pipe(source('common.js'));
|
||||||
if (argv.production) stream = stream.pipe(buffer()).pipe(uglify());
|
if (isProduction) stream = stream.pipe(buffer()).pipe(uglify());
|
||||||
return stream.pipe(gulp.dest('public/js/'));
|
return stream.pipe(gulp.dest('public/js/')).pipe(liveReload());
|
||||||
}
|
}
|
||||||
|
|
||||||
bundler.on('update', function() {
|
bundler.on('update', function() {
|
||||||
rebundle();
|
rebundle();
|
||||||
gutil.log('Rebundle...');
|
gutil.log('Rebundling assets...');
|
||||||
});
|
});
|
||||||
|
|
||||||
bundler.on('log', gutil.log);
|
bundler.on('log', gutil.log);
|
||||||
return rebundle();
|
return rebundle();
|
||||||
}
|
}
|
||||||
|
@ -59,6 +70,7 @@ gulp.task('scripts', () => {scriptTask(false)});
|
||||||
gulp.task('scripts-watch', () => {scriptTask(true)});
|
gulp.task('scripts-watch', () => {scriptTask(true)});
|
||||||
|
|
||||||
gulp.task('default', ['styles', 'scripts-watch'], () => {
|
gulp.task('default', ['styles', 'scripts-watch'], () => {
|
||||||
|
liveReload.listen();
|
||||||
gulp.watch("resources/assets/sass/**/*.scss", ['styles']);
|
gulp.watch("resources/assets/sass/**/*.scss", ['styles']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
10
package.json
10
package.json
|
@ -4,7 +4,8 @@
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"production": "gulp build --production",
|
"production": "gulp build --production",
|
||||||
"dev": "gulp",
|
"dev": "gulp",
|
||||||
"watch": "gulp"
|
"watch": "gulp",
|
||||||
|
"permissions": "chown -R $USER:$USER bootstrap/cache storage public/uploads"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babelify": "^7.3.0",
|
"babelify": "^7.3.0",
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
"gulp": "3.9.1",
|
"gulp": "3.9.1",
|
||||||
"gulp-autoprefixer": "3.1.1",
|
"gulp-autoprefixer": "3.1.1",
|
||||||
"gulp-clean-css": "^3.0.4",
|
"gulp-clean-css": "^3.0.4",
|
||||||
|
"gulp-livereload": "^3.8.1",
|
||||||
"gulp-minify-css": "1.2.4",
|
"gulp-minify-css": "1.2.4",
|
||||||
"gulp-plumber": "1.1.0",
|
"gulp-plumber": "1.1.0",
|
||||||
"gulp-sass": "3.1.0",
|
"gulp-sass": "3.1.0",
|
||||||
|
@ -29,15 +31,17 @@
|
||||||
"angular-sanitize": "^1.5.5",
|
"angular-sanitize": "^1.5.5",
|
||||||
"angular-ui-sortable": "^0.17.0",
|
"angular-ui-sortable": "^0.17.0",
|
||||||
"axios": "^0.16.1",
|
"axios": "^0.16.1",
|
||||||
|
"babel-polyfill": "^6.23.0",
|
||||||
"babel-preset-es2015": "^6.24.1",
|
"babel-preset-es2015": "^6.24.1",
|
||||||
"clipboard": "^1.5.16",
|
"clipboard": "^1.7.1",
|
||||||
"codemirror": "^5.26.0",
|
"codemirror": "^5.26.0",
|
||||||
"dropzone": "^4.0.1",
|
"dropzone": "^4.0.1",
|
||||||
"gulp-util": "^3.0.8",
|
"gulp-util": "^3.0.8",
|
||||||
"markdown-it": "^8.3.1",
|
"markdown-it": "^8.3.1",
|
||||||
"markdown-it-task-lists": "^2.0.0",
|
"markdown-it-task-lists": "^2.0.0",
|
||||||
"moment": "^2.12.0",
|
"moment": "^2.12.0",
|
||||||
"vue": "^2.2.6"
|
"vue": "^2.2.6",
|
||||||
|
"vuedraggable": "^2.14.1"
|
||||||
},
|
},
|
||||||
"browser": {
|
"browser": {
|
||||||
"vue": "vue/dist/vue.common.js"
|
"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:
|
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
|
``` bash
|
||||||
# Build and minify for production
|
# Build assets for development
|
||||||
npm run-script build
|
npm run-script build
|
||||||
|
|
||||||
|
# Build and minify assets for production
|
||||||
|
npm run-script production
|
||||||
|
|
||||||
# Build for dev (With sourcemaps) and watch for changes
|
# Build for dev (With sourcemaps) and watch for changes
|
||||||
npm run-script dev
|
npm run-script dev
|
||||||
```
|
```
|
||||||
|
@ -64,17 +67,19 @@ The BookStack source is provided under the MIT License.
|
||||||
|
|
||||||
## Attribution
|
## 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/)
|
* [Laravel](http://laravel.com/)
|
||||||
* [AngularJS](https://angularjs.org/)
|
* [AngularJS](https://angularjs.org/)
|
||||||
* [jQuery](https://jquery.com/)
|
* [jQuery](https://jquery.com/)
|
||||||
* [TinyMCE](https://www.tinymce.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/)
|
* [jQuery Sortable](https://johnny.github.io/jquery-sortable/)
|
||||||
* [Material Design Iconic Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html)
|
* [Material Design Iconic Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html)
|
||||||
* [Dropzone.js](http://www.dropzonejs.com/)
|
* [Dropzone.js](http://www.dropzonejs.com/)
|
||||||
* [ZeroClipboard](http://zeroclipboard.org/)
|
* [clipboard.js](https://clipboardjs.com/)
|
||||||
* [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html)
|
* [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)
|
* [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/)
|
* [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)
|
* [Snappy (WKHTML2PDF)](https://github.com/barryvdh/laravel-snappy)
|
||||||
* [Laravel IDE helper](https://github.com/barryvdh/laravel-ide-helper)
|
* [Laravel IDE helper](https://github.com/barryvdh/laravel-ide-helper)
|
||||||
* [WKHTMLtoPDF](http://wkhtmltopdf.org/index.html)
|
* [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/css/css');
|
||||||
require('codemirror/mode/clike/clike');
|
require('codemirror/mode/clike/clike');
|
||||||
|
require('codemirror/mode/diff/diff');
|
||||||
require('codemirror/mode/go/go');
|
require('codemirror/mode/go/go');
|
||||||
require('codemirror/mode/htmlmixed/htmlmixed');
|
require('codemirror/mode/htmlmixed/htmlmixed');
|
||||||
require('codemirror/mode/javascript/javascript');
|
require('codemirror/mode/javascript/javascript');
|
||||||
|
@ -17,40 +18,161 @@ require('codemirror/mode/yaml/yaml');
|
||||||
|
|
||||||
const CodeMirror = require('codemirror');
|
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() {
|
module.exports.highlight = function() {
|
||||||
let codeBlocks = document.querySelectorAll('.page-content pre');
|
let codeBlocks = document.querySelectorAll('.page-content pre');
|
||||||
|
|
||||||
for (let i = 0; i < codeBlocks.length; i++) {
|
for (let i = 0; i < codeBlocks.length; i++) {
|
||||||
codeBlocks[i].innerHTML = codeBlocks[i].innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
|
highlightElem(codeBlocks[i]);
|
||||||
let content = codeBlocks[i].textContent;
|
}
|
||||||
|
};
|
||||||
|
|
||||||
CodeMirror(function(elt) {
|
function highlightElem(elem) {
|
||||||
codeBlocks[i].parentNode.replaceChild(elt, codeBlocks[i]);
|
let innerCodeElem = elem.querySelector('code[class^=language-]');
|
||||||
}, {
|
let mode = '';
|
||||||
value: content,
|
if (innerCodeElem !== null) {
|
||||||
mode: "",
|
let langName = innerCodeElem.className.replace('language-', '');
|
||||||
lineNumbers: true,
|
mode = getMode(langName);
|
||||||
theme: 'base16-light',
|
}
|
||||||
readOnly: true
|
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) {
|
module.exports.markdownEditor = function(elem) {
|
||||||
let content = elem.textContent;
|
let content = elem.textContent;
|
||||||
|
|
||||||
let cm = CodeMirror(function(elt) {
|
return CodeMirror(function (elt) {
|
||||||
elem.parentNode.insertBefore(elt, elem);
|
elem.parentNode.insertBefore(elt, elem);
|
||||||
elem.style.display = 'none';
|
elem.style.display = 'none';
|
||||||
}, {
|
}, {
|
||||||
value: content,
|
value: content,
|
||||||
mode: "markdown",
|
mode: "markdown",
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
theme: 'base16-light',
|
theme: 'base16-light',
|
||||||
lineWrapping: true
|
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) {
|
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',
|
ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', '$sce',
|
||||||
function ($scope, $http, $attrs, $interval, $timeout, $sce) {
|
function ($scope, $http, $attrs, $interval, $timeout, $sce) {
|
||||||
|
@ -370,14 +120,8 @@ module.exports = function (ngApp, events) {
|
||||||
saveDraft();
|
saveDraft();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen to shortcuts coming via events
|
// Listen to save draft events from editor
|
||||||
$scope.$on('editor-keydown', (event, data) => {
|
$scope.$on('save-draft', saveDraft);
|
||||||
// Save shortcut (ctrl+s)
|
|
||||||
if (data.keyCode == 83 && (navigator.platform.match("Mac") ? data.metaKey : data.ctrlKey)) {
|
|
||||||
data.preventDefault();
|
|
||||||
saveDraft();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discard the current draft and grab the current page
|
* Discard the current draft and grab the current page
|
||||||
|
@ -385,7 +129,7 @@ module.exports = function (ngApp, events) {
|
||||||
*/
|
*/
|
||||||
$scope.discardDraft = function () {
|
$scope.discardDraft = function () {
|
||||||
let url = window.baseUrl('/ajax/page/' + pageId);
|
let url = window.baseUrl('/ajax/page/' + pageId);
|
||||||
$http.get(url).then((responseData) => {
|
$http.get(url).then(responseData => {
|
||||||
if (autoSave) $interval.cancel(autoSave);
|
if (autoSave) $interval.cancel(autoSave);
|
||||||
$scope.draftText = trans('entities.pages_editing_page');
|
$scope.draftText = trans('entities.pages_editing_page');
|
||||||
$scope.isUpdateDraft = false;
|
$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',
|
ngApp.controller('PageAttachmentController', ['$scope', '$http', '$attrs',
|
||||||
function ($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
|
* TinyMCE
|
||||||
* An angular wrapper around the tinyMCE editor.
|
* An angular wrapper around the tinyMCE editor.
|
||||||
|
@ -187,30 +154,6 @@ module.exports = function (ngApp, events) {
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.tinymce.extraSetups.push(tinyMceSetup);
|
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);
|
tinymce.init(scope.tinymce);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,15 +175,48 @@ module.exports = function (ngApp, events) {
|
||||||
},
|
},
|
||||||
link: function (scope, element, attrs) {
|
link: function (scope, element, attrs) {
|
||||||
|
|
||||||
// Set initial model content
|
|
||||||
element = element.find('textarea').first();
|
|
||||||
|
|
||||||
// Codemirror Setup
|
// Codemirror Setup
|
||||||
|
element = element.find('textarea').first();
|
||||||
let cm = code.markdownEditor(element[0]);
|
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) => {
|
cm.on('change', (instance, changeObj) => {
|
||||||
update(instance);
|
update(instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle scroll to sync display view
|
||||||
cm.on('scroll', instance => {
|
cm.on('scroll', instance => {
|
||||||
// Thanks to http://liuhao.im/english/2015/11/10/the-sync-scroll-of-markdown-editor-in-javascript.html
|
// Thanks to http://liuhao.im/english/2015/11/10/the-sync-scroll-of-markdown-editor-in-javascript.html
|
||||||
let scroll = instance.getScrollInfo();
|
let scroll = instance.getScrollInfo();
|
||||||
|
@ -257,6 +233,166 @@ module.exports = function (ngApp, events) {
|
||||||
scope.$emit('markdown-scroll', totalLines.length);
|
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) {
|
function update(instance) {
|
||||||
let content = instance.getValue();
|
let content = instance.getValue();
|
||||||
element.val(content);
|
element.val(content);
|
||||||
|
@ -267,6 +403,9 @@ module.exports = function (ngApp, events) {
|
||||||
}
|
}
|
||||||
update(cm);
|
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) => {
|
scope.$on('markdown-update', (event, value) => {
|
||||||
cm.setValue(value);
|
cm.setValue(value);
|
||||||
element.val(value);
|
element.val(value);
|
||||||
|
@ -287,8 +426,7 @@ module.exports = function (ngApp, events) {
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
link: function (scope, element, attrs) {
|
link: function (scope, element, attrs) {
|
||||||
|
|
||||||
// Elements
|
// Editor Elements
|
||||||
const $input = element.find('[markdown-input] textarea').first();
|
|
||||||
const $display = element.find('.markdown-display').first();
|
const $display = element.find('.markdown-display').first();
|
||||||
const $insertImage = element.find('button[data-action="insertImage"]');
|
const $insertImage = element.find('button[data-action="insertImage"]');
|
||||||
const $insertEntityLink = element.find('button[data-action="insertEntityLink"]');
|
const $insertEntityLink = element.find('button[data-action="insertEntityLink"]');
|
||||||
|
@ -299,11 +437,9 @@ module.exports = function (ngApp, events) {
|
||||||
window.open(this.getAttribute('href'));
|
window.open(this.getAttribute('href'));
|
||||||
});
|
});
|
||||||
|
|
||||||
let currentCaretPos = 0;
|
// Editor UI Actions
|
||||||
|
$insertEntityLink.click(e => {scope.$broadcast('md-insert-link');});
|
||||||
$input.blur(event => {
|
$insertImage.click(e => {scope.$broadcast('md-insert-image');});
|
||||||
currentCaretPos = $input[0].selectionStart;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle scroll sync event from editor scroll
|
// Handle scroll sync event from editor scroll
|
||||||
$rootScope.$on('markdown-scroll', (event, lineCount) => {
|
$rootScope.$on('markdown-scroll', (event, lineCount) => {
|
||||||
|
@ -315,140 +451,6 @@ module.exports = function (ngApp, events) {
|
||||||
}, {queue: false, duration: 200, easing: 'linear'});
|
}, {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) {
|
ngApp.directive('entityLinkSelector', [function($http) {
|
||||||
return {
|
return {
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
|
@ -711,6 +531,7 @@ module.exports = function (ngApp, events) {
|
||||||
function hide() {
|
function hide() {
|
||||||
element.fadeOut(240);
|
element.fadeOut(240);
|
||||||
}
|
}
|
||||||
|
scope.hide = hide;
|
||||||
|
|
||||||
// Listen to confirmation of entity selections (doubleclick)
|
// Listen to confirmation of entity selections (doubleclick)
|
||||||
events.listen('entity-select-confirm', entity => {
|
events.listen('entity-select-confirm', entity => {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
require("babel-polyfill");
|
||||||
|
|
||||||
// Url retrieval function
|
// Url retrieval function
|
||||||
window.baseUrl = function(path) {
|
window.baseUrl = function(path) {
|
||||||
|
@ -17,11 +18,9 @@ let axiosInstance = axios.create({
|
||||||
'baseURL': window.baseUrl('')
|
'baseURL': window.baseUrl('')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
window.$http = axiosInstance;
|
||||||
Vue.prototype.$http = axiosInstance;
|
Vue.prototype.$http = axiosInstance;
|
||||||
|
|
||||||
require("./vues/vues");
|
|
||||||
|
|
||||||
|
|
||||||
// AngularJS - Create application and load components
|
// AngularJS - Create application and load components
|
||||||
const angular = require("angular");
|
const angular = require("angular");
|
||||||
|
@ -64,11 +63,12 @@ class EventManager {
|
||||||
window.Events = new EventManager();
|
window.Events = new EventManager();
|
||||||
Vue.prototype.$events = window.Events;
|
Vue.prototype.$events = window.Events;
|
||||||
|
|
||||||
|
require("./vues/vues");
|
||||||
|
require("./components");
|
||||||
|
|
||||||
// Load in angular specific items
|
// Load in angular specific items
|
||||||
const Services = require('./services');
|
|
||||||
const Directives = require('./directives');
|
const Directives = require('./directives');
|
||||||
const Controllers = require('./controllers');
|
const Controllers = require('./controllers');
|
||||||
Services(ngApp, window.Events);
|
|
||||||
Directives(ngApp, window.Events);
|
Directives(ngApp, window.Events);
|
||||||
Controllers(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
|
// Detect IE for css
|
||||||
if(navigator.userAgent.indexOf('MSIE')!==-1
|
if(navigator.userAgent.indexOf('MSIE')!==-1
|
||||||
|| navigator.appVersion.indexOf('Trident/') > 0
|
|| navigator.appVersion.indexOf('Trident/') > 0
|
||||||
|| navigator.userAgent.indexOf('Safari') !== -1){
|
|| navigator.userAgent.indexOf('Safari') !== -1){
|
||||||
$('body').addClass('flexbox-support');
|
document.body.classList.add('flexbox-support');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page specific items
|
// Page specific items
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
const Code = require('../code');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle pasting images from clipboard.
|
* Handle pasting images from clipboard.
|
||||||
* @param e - event
|
* @param e - event
|
||||||
|
@ -50,23 +52,183 @@ function editorPaste(e, editor) {
|
||||||
function registerEditorShortcuts(editor) {
|
function registerEditorShortcuts(editor) {
|
||||||
// Headers
|
// Headers
|
||||||
for (let i = 1; i < 5; i++) {
|
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
|
// Other block shortcuts
|
||||||
editor.addShortcut('meta+q', '', ['FormatBlock', false, 'blockquote']);
|
editor.shortcuts.add('meta+5', '', ['FormatBlock', false, 'p']);
|
||||||
editor.addShortcut('meta+d', '', ['FormatBlock', false, 'p']);
|
editor.shortcuts.add('meta+d', '', ['FormatBlock', false, 'p']);
|
||||||
editor.addShortcut('meta+e', '', ['FormatBlock', false, 'pre']);
|
editor.shortcuts.add('meta+6', '', ['FormatBlock', false, 'blockquote']);
|
||||||
editor.addShortcut('meta+shift+E', '', ['FormatBlock', false, 'code']);
|
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() {
|
module.exports = function() {
|
||||||
|
hrPlugin();
|
||||||
|
codePlugin();
|
||||||
let settings = {
|
let settings = {
|
||||||
selector: '#html-editor',
|
selector: '#html-editor',
|
||||||
content_css: [
|
content_css: [
|
||||||
window.baseUrl('/css/styles.css'),
|
window.baseUrl('/css/styles.css'),
|
||||||
window.baseUrl('/libs/material-design-iconic-font/css/material-design-iconic-font.min.css')
|
window.baseUrl('/libs/material-design-iconic-font/css/material-design-iconic-font.min.css')
|
||||||
],
|
],
|
||||||
|
branding: false,
|
||||||
body_class: 'page-content',
|
body_class: 'page-content',
|
||||||
browser_spellcheck: true,
|
browser_spellcheck: true,
|
||||||
relative_urls: false,
|
relative_urls: false,
|
||||||
|
@ -77,10 +239,10 @@ module.exports = function() {
|
||||||
paste_data_images: false,
|
paste_data_images: false,
|
||||||
extended_valid_elements: 'pre[*]',
|
extended_valid_elements: 'pre[*]',
|
||||||
automatic_uploads: false,
|
automatic_uploads: false,
|
||||||
valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
|
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 codesample",
|
plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor",
|
||||||
imagetools_toolbar: 'imageoptions',
|
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;}",
|
content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}",
|
||||||
style_formats: [
|
style_formats: [
|
||||||
{title: "Header Large", format: "h2"},
|
{title: "Header Large", format: "h2"},
|
||||||
|
@ -89,20 +251,25 @@ module.exports = function() {
|
||||||
{title: "Header Tiny", format: "h5"},
|
{title: "Header Tiny", format: "h5"},
|
||||||
{title: "Paragraph", format: "p", exact: true, classes: ''},
|
{title: "Paragraph", format: "p", exact: true, classes: ''},
|
||||||
{title: "Blockquote", format: "blockquote"},
|
{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: "Inline Code", icon: "code", inline: "code"},
|
||||||
{title: "Callouts", items: [
|
{title: "Callouts", items: [
|
||||||
{title: "Success", block: 'p', exact: true, attributes : {'class' : 'callout success'}},
|
{title: "Info", format: 'calloutinfo'},
|
||||||
{title: "Info", block: 'p', exact: true, attributes : {'class' : 'callout info'}},
|
{title: "Success", format: 'calloutsuccess'},
|
||||||
{title: "Warning", block: 'p', exact: true, attributes : {'class' : 'callout warning'}},
|
{title: "Warning", format: 'calloutwarning'},
|
||||||
{title: "Danger", block: 'p', exact: true, attributes : {'class' : 'callout danger'}}
|
{title: "Danger", format: 'calloutdanger'}
|
||||||
]}
|
]},
|
||||||
],
|
],
|
||||||
style_formats_merge: false,
|
style_formats_merge: false,
|
||||||
formats: {
|
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'},
|
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'},
|
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'},
|
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) {
|
file_browser_callback: function (field_name, url, type, win) {
|
||||||
|
|
||||||
|
@ -116,7 +283,7 @@ module.exports = function() {
|
||||||
|
|
||||||
if (type === 'image') {
|
if (type === 'image') {
|
||||||
// Show image manager
|
// Show image manager
|
||||||
window.ImageManager.showExternal(function (image) {
|
window.ImageManager.show(function (image) {
|
||||||
|
|
||||||
// Set popover link input to image url then fire change event
|
// Set popover link input to image url then fire change event
|
||||||
// to ensure the new value sticks
|
// to ensure the new value sticks
|
||||||
|
@ -198,7 +365,7 @@ module.exports = function() {
|
||||||
icon: 'image',
|
icon: 'image',
|
||||||
tooltip: 'Insert an image',
|
tooltip: 'Insert an image',
|
||||||
onclick: function () {
|
onclick: function () {
|
||||||
window.ImageManager.showExternal(function (image) {
|
window.ImageManager.show(function (image) {
|
||||||
let html = `<a href="${image.url}" target="_blank">`;
|
let html = `<a href="${image.url}" target="_blank">`;
|
||||||
html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
|
html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
|
||||||
html += '</a>';
|
html += '</a>';
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
"use strict";
|
|
||||||
// Configure ZeroClipboard
|
|
||||||
const Clipboard = require("clipboard");
|
const Clipboard = require("clipboard");
|
||||||
const Code = require('../code');
|
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) {
|
updateSearch(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.location = '/search?term=' + encodeURIComponent(this.termString);
|
window.location = window.baseUrl('/search?term=' + encodeURIComponent(this.termString));
|
||||||
},
|
},
|
||||||
|
|
||||||
enableDate(optionName) {
|
enableDate(optionName) {
|
||||||
|
@ -192,4 +192,4 @@ function created() {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
data, computed, methods, created
|
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 = {
|
let vueMapping = {
|
||||||
'search-system': require('./search'),
|
'search-system': require('./search'),
|
||||||
'entity-dashboard': require('./entity-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 => {
|
Object.keys(vueMapping).forEach(id => {
|
||||||
if (exists(id)) {
|
if (exists(id)) {
|
||||||
let config = vueMapping[id];
|
let config = vueMapping[id];
|
||||||
config.el = '#' + id;
|
config.el = '#' + id;
|
||||||
new Vue(config);
|
window.vues[id] = new Vue(config);
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -36,41 +36,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.anim.notification {
|
.anim.menuIn {
|
||||||
transform: translate3d(580px, 0, 0);
|
transform-origin: 100% 0%;
|
||||||
animation-name: notification;
|
animation-name: menuIn;
|
||||||
animation-duration: 3s;
|
animation-duration: 120ms;
|
||||||
animation-timing-function: ease-in-out;
|
animation-delay: 0s;
|
||||||
animation-fill-mode: forwards;
|
animation-timing-function: cubic-bezier(.62, .28, .23, .99);
|
||||||
&.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes menuIn {
|
@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 {
|
@keyframes loadingBob {
|
||||||
0% {
|
0% {
|
||||||
transform: translate3d(0, 0, 0);
|
transform: translate3d(0, 0, 0);
|
||||||
|
|
|
@ -248,6 +248,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
-webkit-font-variant-ligatures: contextual;
|
-webkit-font-variant-ligatures: contextual;
|
||||||
font-variant-ligatures: contextual;
|
font-variant-ligatures: contextual;
|
||||||
|
&:after {
|
||||||
|
content: none;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.CodeMirror-wrap pre {
|
.CodeMirror-wrap pre {
|
||||||
word-wrap: break-word;
|
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);
|
background-color: rgba(0, 0, 0, 0.333);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 95536;
|
z-index: 95536;
|
||||||
|
@ -466,4 +527,17 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||||
|
|
||||||
.image-picker .none {
|
.image-picker .none {
|
||||||
display: 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 {
|
#markdown-editor {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
textarea {
|
#markdown-editor-input {
|
||||||
font-family: 'Roboto Mono', monospace;
|
font-family: 'Roboto Mono', monospace;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
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: 0;
|
||||||
border-bottom: 2px solid #DDD;
|
border-bottom: 2px solid #DDD;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
|
@ -142,7 +142,6 @@ form.search-box {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
padding: 0 $-xs;
|
padding: 0 $-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
.faded {
|
.faded {
|
||||||
a, button, span, span > div {
|
a, button, span, span > div {
|
||||||
color: #666;
|
color: #666;
|
||||||
|
@ -155,7 +154,6 @@ form.search-box {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.faded span.faded-text {
|
.faded span.faded-text {
|
||||||
|
@ -175,6 +173,15 @@ form.search-box {
|
||||||
&:last-child {
|
&:last-child {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
&:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.action-buttons .dropdown-container:last-child a {
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: $-s;
|
||||||
}
|
}
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
text-align: right;
|
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 {
|
.nav-tabs {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
a, .tab-item {
|
a, .tab-item {
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
.inset-list {
|
.inset-list {
|
||||||
display: none;
|
display: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-bottom: $-l;
|
|
||||||
}
|
}
|
||||||
h5 {
|
h5 {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -22,6 +21,9 @@
|
||||||
border-left-color: $color-page-draft;
|
border-left-color: $color-page-draft;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.entity-list-item {
|
||||||
|
margin-bottom: $-m;
|
||||||
|
}
|
||||||
hr {
|
hr {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
@ -51,23 +53,6 @@
|
||||||
margin-right: $-s;
|
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 {
|
.sidebar-page-nav {
|
||||||
$nav-indent: $-s;
|
$nav-indent: $-s;
|
||||||
|
@ -171,7 +156,7 @@
|
||||||
background-color: rgba($color-chapter, 0.12);
|
background-color: rgba($color-chapter, 0.12);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.chapter-toggle {
|
[chapter-toggle] {
|
||||||
padding-left: $-s;
|
padding-left: $-s;
|
||||||
}
|
}
|
||||||
.list-item-chapter {
|
.list-item-chapter {
|
||||||
|
@ -336,8 +321,10 @@ ul.pagination {
|
||||||
h4, a {
|
h4, a {
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
p {
|
.entity-item-snippet {
|
||||||
display: none;
|
display: none;
|
||||||
|
}
|
||||||
|
p {
|
||||||
font-size: $fs-m * 0.8;
|
font-size: $fs-m * 0.8;
|
||||||
padding-top: $-xs;
|
padding-top: $-xs;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -226,7 +226,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
}
|
}
|
||||||
.tags td {
|
.tags td, .tag-table > div > div > div {
|
||||||
padding-right: $-s;
|
padding-right: $-s;
|
||||||
padding-top: $-s;
|
padding-top: $-s;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -67,4 +67,17 @@ table.file-table {
|
||||||
.ui-sortable-helper {
|
.ui-sortable-helper {
|
||||||
display: table;
|
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;
|
font-size: 12px;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
border: 1px solid #DDD;
|
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 {
|
blockquote {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -182,6 +205,7 @@ pre code {
|
||||||
border: 0;
|
border: 0;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
display: block;
|
display: block;
|
||||||
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Text colors
|
* 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
|
// Loading icon
|
||||||
$loadingSize: 10px;
|
$loadingSize: 10px;
|
||||||
.loading-container {
|
.loading-container {
|
||||||
|
@ -151,7 +113,7 @@ $loadingSize: 10px;
|
||||||
|
|
||||||
// Back to top link
|
// Back to top link
|
||||||
$btt-size: 40px;
|
$btt-size: 40px;
|
||||||
#back-to-top {
|
[back-to-top] {
|
||||||
background-color: $primary;
|
background-color: $primary;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: $-m;
|
bottom: $-m;
|
||||||
|
|
|
@ -20,5 +20,13 @@ return [
|
||||||
'image_preview' => 'Image Preview',
|
'image_preview' => 'Image Preview',
|
||||||
'image_upload_success' => 'Image uploaded successfully',
|
'image_upload_success' => 'Image uploaded successfully',
|
||||||
'image_update_success' => 'Image details successfully updated',
|
'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',
|
'nl' => 'Nederlands',
|
||||||
'pt_BR' => 'Português do Brasil',
|
'pt_BR' => 'Português do Brasil',
|
||||||
'sk' => 'Slovensky',
|
'sk' => 'Slovensky',
|
||||||
|
'ja' => '日本語',
|
||||||
|
'pl' => 'Polski',
|
||||||
]
|
]
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
];
|
];
|
||||||
|
|
|
@ -10,7 +10,7 @@ return [
|
||||||
| these language lines according to your application's requirements.
|
| 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.",
|
'throttle' => "Trop d'essais, veuillez réessayer dans :seconds secondes.",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,7 @@ return [
|
||||||
'password' => 'Mot de passe',
|
'password' => 'Mot de passe',
|
||||||
'password_confirm' => 'Confirmez le mot de passe',
|
'password_confirm' => 'Confirmez le mot de passe',
|
||||||
'password_hint' => 'Doit faire plus de 5 caractères',
|
'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',
|
'remember_me' => 'Se souvenir de moi',
|
||||||
'ldap_email_hint' => "Merci d'entrer une adresse e-mail pour ce compte",
|
'ldap_email_hint' => "Merci d'entrer une adresse e-mail pour ce compte",
|
||||||
'create_account' => 'Créer un 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",
|
'social_registration_text' => "S'inscrire et se connecter avec un réseau social",
|
||||||
|
|
||||||
'register_thanks' => 'Merci pour votre enregistrement',
|
'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",
|
'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)',
|
'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.',
|
'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_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.',
|
'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 Confirmation
|
||||||
*/
|
*/
|
||||||
'email_confirm_subject' => 'Confirmez votre adresse e-mail pour :appName',
|
'email_confirm_subject' => 'Confirmez votre adresse e-mail pour :appName',
|
||||||
'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName!',
|
'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName !',
|
||||||
'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous:',
|
'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous :',
|
||||||
'email_confirm_action' => 'Confirmez votre adresse e-mail',
|
'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_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_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',
|
'email_not_confirmed' => 'Adresse e-mail non confirmée',
|
||||||
|
|
|
@ -9,7 +9,7 @@ return [
|
||||||
'back' => 'Retour',
|
'back' => 'Retour',
|
||||||
'save' => 'Enregistrer',
|
'save' => 'Enregistrer',
|
||||||
'continue' => 'Continuer',
|
'continue' => 'Continuer',
|
||||||
'select' => 'Selectionner',
|
'select' => 'Sélectionner',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Form Labels
|
* Form Labels
|
||||||
|
@ -53,6 +53,6 @@ return [
|
||||||
/**
|
/**
|
||||||
* Email Content
|
* 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',
|
'email_rights' => 'Tous droits réservés',
|
||||||
];
|
];
|
||||||
|
|
|
@ -12,7 +12,7 @@ return [
|
||||||
'recently_update' => 'Mis à jour récemment',
|
'recently_update' => 'Mis à jour récemment',
|
||||||
'recently_viewed' => 'Vus récemment',
|
'recently_viewed' => 'Vus récemment',
|
||||||
'recent_activity' => 'Activité récente',
|
'recent_activity' => 'Activité récente',
|
||||||
'create_now' => 'En créer un récemment',
|
'create_now' => 'En créer un maintenant',
|
||||||
'revisions' => 'Révisions',
|
'revisions' => 'Révisions',
|
||||||
'meta_created' => 'Créé :timeLength',
|
'meta_created' => 'Créé :timeLength',
|
||||||
'meta_created_name' => 'Créé :timeLength par :user',
|
'meta_created_name' => 'Créé :timeLength par :user',
|
||||||
|
@ -59,8 +59,8 @@ return [
|
||||||
'books_create' => 'Créer un nouveau livre',
|
'books_create' => 'Créer un nouveau livre',
|
||||||
'books_delete' => 'Supprimer un livre',
|
'books_delete' => 'Supprimer un livre',
|
||||||
'books_delete_named' => 'Supprimer le livre :bookName',
|
'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_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_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer ce livre ?',
|
||||||
'books_edit' => 'Modifier le livre',
|
'books_edit' => 'Modifier le livre',
|
||||||
'books_edit_named' => 'Modifier le livre :bookName',
|
'books_edit_named' => 'Modifier le livre :bookName',
|
||||||
'books_form_book_name' => 'Nom du livre',
|
'books_form_book_name' => 'Nom du livre',
|
||||||
|
@ -90,18 +90,18 @@ return [
|
||||||
'chapters_create' => 'Créer un nouveau chapitre',
|
'chapters_create' => 'Créer un nouveau chapitre',
|
||||||
'chapters_delete' => 'Supprimer le chapitre',
|
'chapters_delete' => 'Supprimer le chapitre',
|
||||||
'chapters_delete_named' => 'Supprimer le chapitre :chapterName',
|
'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_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_delete_confirm' => 'Etes-vous sûr(e) de vouloir supprimer ce chapitre ?',
|
||||||
'chapters_edit' => 'Modifier le chapitre',
|
'chapters_edit' => 'Modifier le chapitre',
|
||||||
'chapters_edit_named' => 'Modifier le chapitre :chapterName',
|
'chapters_edit_named' => 'Modifier le chapitre :chapterName',
|
||||||
'chapters_save' => 'Enregistrer le chapitre',
|
'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',
|
'chapters_move_named' => 'Déplacer le chapitre :chapterName',
|
||||||
'chapter_move_success' => 'Chapitre déplacé dans :bookName',
|
'chapter_move_success' => 'Chapitre déplacé dans :bookName',
|
||||||
'chapters_permissions' => 'Permissions du chapitre',
|
'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_active' => 'Permissions du chapitre activées',
|
||||||
'chapters_permissions_success' => 'Permissions du chapitres mises à jour',
|
'chapters_permissions_success' => 'Permissions du chapitre mises à jour',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pages
|
* Pages
|
||||||
|
@ -118,8 +118,8 @@ return [
|
||||||
'pages_delete_draft' => 'Supprimer le brouillon',
|
'pages_delete_draft' => 'Supprimer le brouillon',
|
||||||
'pages_delete_success' => 'Page supprimée',
|
'pages_delete_success' => 'Page supprimée',
|
||||||
'pages_delete_draft_success' => 'Brouillon supprimé',
|
'pages_delete_draft_success' => 'Brouillon supprimé',
|
||||||
'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page?',
|
'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_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon ?',
|
||||||
'pages_editing_named' => 'Modification de la page :pageName',
|
'pages_editing_named' => 'Modification de la page :pageName',
|
||||||
'pages_edit_toggle_header' => 'Afficher/cacher l\'en-tête',
|
'pages_edit_toggle_header' => 'Afficher/cacher l\'en-tête',
|
||||||
'pages_edit_save_draft' => 'Enregistrer le brouillon',
|
'pages_edit_save_draft' => 'Enregistrer le brouillon',
|
||||||
|
@ -131,7 +131,7 @@ return [
|
||||||
'pages_edit_discard_draft' => 'Ecarter le brouillon',
|
'pages_edit_discard_draft' => 'Ecarter le brouillon',
|
||||||
'pages_edit_set_changelog' => 'Remplir le journal des changements',
|
'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_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_save' => 'Enregistrez la page',
|
||||||
'pages_title' => 'Titre de la page',
|
'pages_title' => 'Titre de la page',
|
||||||
'pages_name' => 'Nom de la page',
|
'pages_name' => 'Nom de la page',
|
||||||
|
@ -139,7 +139,7 @@ return [
|
||||||
'pages_md_preview' => 'Prévisualisation',
|
'pages_md_preview' => 'Prévisualisation',
|
||||||
'pages_md_insert_image' => 'Insérer une image',
|
'pages_md_insert_image' => 'Insérer une image',
|
||||||
'pages_md_insert_link' => 'Insérer un lien',
|
'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' => 'Déplacer la page',
|
||||||
'pages_move_success' => 'Page déplacée à ":parentName"',
|
'pages_move_success' => 'Page déplacée à ":parentName"',
|
||||||
'pages_permissions' => 'Permissions de la page',
|
'pages_permissions' => 'Permissions de la page',
|
||||||
|
@ -160,15 +160,15 @@ return [
|
||||||
'pages_initial_revision' => 'Publication initiale',
|
'pages_initial_revision' => 'Publication initiale',
|
||||||
'pages_initial_name' => 'Nouvelle page',
|
'pages_initial_name' => 'Nouvelle page',
|
||||||
'pages_editing_draft_notification' => 'Vous éditez actuellement un brouillon qui a été sauvé :timeDiff.',
|
'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' => [
|
'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',
|
'start_b' => ':userName a commencé à éditer cette page',
|
||||||
'time_a' => 'depuis la dernière sauvegarde',
|
'time_a' => 'depuis la dernière sauvegarde',
|
||||||
'time_b' => 'dans les :minCount dernières minutes',
|
'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
|
* Editor sidebar
|
||||||
|
@ -210,9 +210,9 @@ return [
|
||||||
*/
|
*/
|
||||||
'profile_user_for_x' => 'Utilisateur depuis :time',
|
'profile_user_for_x' => 'Utilisateur depuis :time',
|
||||||
'profile_created_content' => 'Contenu créé',
|
'profile_created_content' => 'Contenu créé',
|
||||||
'profile_not_created_pages' => ':userName n\'a pas créé de pages',
|
'profile_not_created_pages' => ':userName n\'a pas créé de page',
|
||||||
'profile_not_created_chapters' => ':userName n\'a pas créé de chapitres',
|
'profile_not_created_chapters' => ':userName n\'a pas créé de chapitre',
|
||||||
'profile_not_created_books' => ':userName n\'a pas créé de livres',
|
'profile_not_created_books' => ':userName n\'a pas créé de livre',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comments
|
* Comments
|
||||||
|
|
|
@ -18,21 +18,21 @@ return [
|
||||||
'ldap_fail_anonymous' => 'L\'accès LDAP anonyme n\'a pas abouti',
|
'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_fail_authed' => 'L\'accès LDAP n\'a pas abouti avec cet utilisateur et ce mot de passe',
|
||||||
'ldap_extension_not_installed' => 'L\'extention LDAP PHP n\'est pas installée',
|
'ldap_extension_not_installed' => 'L\'extention LDAP PHP n\'est pas installée',
|
||||||
'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed',
|
'ldap_cannot_connect' => 'Impossible de se connecter au serveur LDAP, la connexion initiale a échoué',
|
||||||
'social_no_action_defined' => 'No action defined',
|
'social_no_action_defined' => 'Pas d\'action définie',
|
||||||
'social_account_in_use' => 'Cet compte :socialAccount est déjà utilisé. Essayez de vous connecter via :socialAccount.',
|
'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_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_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_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_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_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_found' => 'Pilote de compte social absent',
|
||||||
'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
|
'social_driver_not_configured' => 'Vos préférences pour le compte :socialAccount sont incorrectes.',
|
||||||
|
|
||||||
// System
|
// 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_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.',
|
'server_upload_limit' => 'La taille du fichier est trop grande.',
|
||||||
'image_upload_error' => 'Une erreur est survenue pendant l\'envoi de l\'image',
|
'image_upload_error' => 'Une erreur est survenue pendant l\'envoi de l\'image',
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ return [
|
||||||
|
|
||||||
// Roles
|
// Roles
|
||||||
'role_cannot_be_edited' => 'Ce rôle ne peut pas être modifié',
|
'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',
|
'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
|
// Error pages
|
||||||
|
|
|
@ -16,7 +16,7 @@ return [
|
||||||
'password' => 'Les mots de passe doivent faire au moins 6 caractères et correspondre à la confirmation.',
|
'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.",
|
'user' => "Nous n'avons pas trouvé d'utilisateur avec cette adresse.",
|
||||||
'token' => 'Le jeton de réinitialisation est invalide.',
|
'token' => 'Le jeton de réinitialisation est invalide.',
|
||||||
'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe!',
|
'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe !',
|
||||||
'reset' => 'Votre mot de passe a été réinitialisé!',
|
'reset' => 'Votre mot de passe a été réinitialisé !',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -19,27 +19,27 @@ return [
|
||||||
'app_settings' => 'Préférences de l\'application',
|
'app_settings' => 'Préférences de l\'application',
|
||||||
'app_name' => 'Nom 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_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_name_header' => 'Afficher le nom dans l\'en-tête ?',
|
||||||
'app_public_viewing' => 'Accepter le visionnage public des pages?',
|
'app_public_viewing' => 'Accepter le visionnage public des pages ?',
|
||||||
'app_secure_images' => 'Activer l\'ajout d\'image sécurisé?',
|
'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_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' => 'Editeur des pages',
|
||||||
'app_editor_desc' => 'Sélectionnez l\'éditeur qui sera utilisé pour modifier les 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' => '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' => 'Logo de l\'Application',
|
||||||
'app_logo_desc' => 'Cette image doit faire 43px de hauteur. <br>Les images plus larges seront réduites.',
|
'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' => '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
|
* Registration settings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'reg_settings' => 'Préférence pour l\'inscription',
|
'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_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_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' => 'Restreindre l\'inscription à un domaine',
|
||||||
'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les utilisateur recevront un e-mail de confirmation à cette adresse. <br> Les utilisateurs pourront changer leur adresse après inscription s\'ils le souhaitent.',
|
'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les 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_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_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_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_delete_success' => 'Le rôle a été supprimé avec succès',
|
||||||
'role_edit' => 'Modifier le rôle',
|
'role_edit' => 'Modifier le rôle',
|
||||||
'role_details' => 'Détails du 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_desc' => 'Courte description du rôle',
|
||||||
'role_system' => 'Permissions système',
|
'role_system' => 'Permissions système',
|
||||||
'role_manage_users' => 'Gérer les utilisateurs',
|
'role_manage_users' => 'Gérer les utilisateurs',
|
||||||
'role_manage_roles' => 'Gérer les rôles et permissions',
|
'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_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_manage_settings' => 'Gérer les préférences de l\'application',
|
||||||
'role_asset' => 'Asset Permissions',
|
'role_asset' => 'Asset Permissions',
|
||||||
'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
|
'role_asset_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' => 'Supprimer un utilisateur',
|
||||||
'users_delete_named' => 'Supprimer l\'utilisateur :userName',
|
'users_delete_named' => 'Supprimer l\'utilisateur :userName',
|
||||||
'users_delete_warning' => 'Ceci va supprimer \':userName\' du système.',
|
'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_delete_success' => 'Utilisateurs supprimés avec succès',
|
||||||
'users_edit' => 'Modifier l\'utilisateur',
|
'users_edit' => 'Modifier l\'utilisateur',
|
||||||
'users_edit_profile' => 'Modifier le profil',
|
'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_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_connect' => 'Connecter le compte',
|
||||||
'users_social_disconnect' => 'Dé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',
|
'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')
|
@yield('content')
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div id="back-to-top">
|
<div back-to-top>
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<i class="zmdi zmdi-chevron-up"></i> <span>{{ trans('common.back_to_top') }}</span>
|
<i class="zmdi zmdi-chevron-up"></i> <span>{{ trans('common.back_to_top') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
<div class="book entity-list-item" data-entity-type="book" data-entity-id="{{$book->id}}">
|
<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>
|
<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))
|
<div class="entity-item-snippet">
|
||||||
<p class="text-muted">{!! $book->searchSnippet !!}</p>
|
@if(isset($book->searchSnippet))
|
||||||
@else
|
<p class="text-muted">{!! $book->searchSnippet !!}</p>
|
||||||
<p class="text-muted">{{ $book->getExcerpt() }}</p>
|
@else
|
||||||
@endif
|
<p class="text-muted">{{ $book->getExcerpt() }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -5,10 +5,10 @@
|
||||||
<div class="faded-small toolbar">
|
<div class="faded-small toolbar">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6 faded">
|
<div class="col-sm-6 col-xs-1 faded">
|
||||||
@include('books._breadcrumbs', ['book' => $book])
|
@include('books._breadcrumbs', ['book' => $book])
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6 col-xs-11">
|
||||||
<div class="action-buttons faded">
|
<div class="action-buttons faded">
|
||||||
<span dropdown class="dropdown-container">
|
<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>
|
<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>
|
||||||
|
|
||||||
|
|
||||||
<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="row">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
|
|
||||||
<h1>{{$book->name}}</h1>
|
<h1>{{$book->name}}</h1>
|
||||||
<div class="book-content" v-if="!searching">
|
<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>
|
<div class="page-list" v-pre>
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -72,9 +72,15 @@
|
||||||
@else
|
@else
|
||||||
<p class="text-muted">{{ trans('entities.books_empty_contents') }}</p>
|
<p class="text-muted">{{ trans('entities.books_empty_contents') }}</p>
|
||||||
<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>
|
<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>
|
<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>
|
<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>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
@endif
|
@endif
|
||||||
|
@ -106,13 +112,13 @@
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="search-box">
|
<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') }}">
|
<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 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>
|
<button v-if="searching" v-cloak class="text-neg" v-on:click="clearSearch()" type="button"><i class="zmdi zmdi-close"></i></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="activity">
|
<div class="activity">
|
||||||
<h3>{{ trans('entities.recent_activity') }}</h3>
|
<h3>{{ trans('entities.recent_activity') }}</h3>
|
||||||
@include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)])
|
@include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)])
|
||||||
|
@ -121,4 +127,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@stop
|
@stop
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<h4>
|
<h4>
|
||||||
@if (isset($showPath) && $showPath)
|
@if (isset($showPath) && $showPath)
|
||||||
<a href="{{ $chapter->book->getUrl() }}" class="text-book">
|
<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>
|
</a>
|
||||||
<span class="text-muted"> » </span>
|
<span class="text-muted"> » </span>
|
||||||
@endif
|
@endif
|
||||||
|
@ -10,14 +10,18 @@
|
||||||
<i class="zmdi zmdi-collection-bookmark"></i><span class="entity-list-item-name">{{ $chapter->name }}</span>
|
<i class="zmdi zmdi-collection-bookmark"></i><span class="entity-list-item-name">{{ $chapter->name }}</span>
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
@if(isset($chapter->searchSnippet))
|
|
||||||
<p class="text-muted">{!! $chapter->searchSnippet !!}</p>
|
<div class="entity-item-snippet">
|
||||||
@else
|
@if(isset($chapter->searchSnippet))
|
||||||
<p class="text-muted">{{ $chapter->getExcerpt() }}</p>
|
<p class="text-muted">{!! $chapter->searchSnippet !!}</p>
|
||||||
@endif
|
@else
|
||||||
|
<p class="text-muted">{{ $chapter->getExcerpt() }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@if(!isset($hidePages) && count($chapter->pages) > 0)
|
@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">
|
<div class="inset-list">
|
||||||
@foreach($chapter->pages as $page)
|
@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>
|
<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="faded-small toolbar">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<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])
|
@include('chapters._breadcrumbs', ['chapter' => $chapter])
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 faded">
|
<div class="col-sm-6 col-xs-9 faded">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<span dropdown class="dropdown-container">
|
<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>
|
<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>
|
||||||
|
|
||||||
|
|
||||||
<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="row">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<h1>{{ $chapter->name }}</h1>
|
<h1>{{ $chapter->name }}</h1>
|
||||||
<div class="chapter-content" v-if="!searching">
|
<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)
|
@if(count($pages) > 0)
|
||||||
<div class="page-list">
|
<div class="page-list">
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="search-box">
|
<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') }}">
|
<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 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>
|
<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 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-body small flex-child">
|
||||||
<div class="popup-header primary-background">
|
<div class="popup-header primary-background">
|
||||||
<div class="popup-title">{{ trans('entities.entity_select') }}</div>
|
<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>
|
</div>
|
||||||
@include('components.entity-selector', ['name' => 'entity-selector'])
|
@include('components.entity-selector', ['name' => 'entity-selector'])
|
||||||
<div class="popup-footer">
|
<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 id="image-manager" image-type="{{ $imageType }}" uploaded-to="{{ $uploaded_to or 0 }}">
|
||||||
<div class="overlay" ng-cloak ng-click="hide()">
|
<div overlay v-cloak>
|
||||||
<div class="popup-body" ng-click="$event.stopPropagation()">
|
<div class="popup-body" @click.stop="">
|
||||||
|
|
||||||
<div class="popup-header primary-background">
|
<div class="popup-header primary-background">
|
||||||
<div class="popup-title">{{ trans('components.image_select') }}</div>
|
<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>
|
||||||
|
|
||||||
<div class="flex-fill image-manager-body">
|
<div class="flex-fill image-manager-body">
|
||||||
|
|
||||||
<div class="image-manager-content">
|
<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="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_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') }}" 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_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') }}" 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_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>
|
</div>
|
||||||
<div ng-show="view === 'all'" >
|
<div v-show="view === 'all'" >
|
||||||
<form ng-submit="searchImages()" class="contained-search-box">
|
<form @submit="searchImages" class="contained-search-box">
|
||||||
<input type="text" placeholder="{{ trans('components.image_search_hint') }}" ng-model="searchTerm">
|
<input placeholder="{{ trans('components.image_search_hint') }}" v-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 :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" type="submit"><i class="zmdi zmdi-search"></i></button>
|
<button title="{{ trans('common.search') }}" class="text-button"><i class="zmdi zmdi-search"></i></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="image-manager-list">
|
<div class="image-manager-list">
|
||||||
<div ng-repeat="image in images">
|
<div v-if="images.length > 0" v-for="(image, idx) in images">
|
||||||
<div class="image anim fadeIn" ng-style="{animationDelay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'}"
|
<div class="image anim fadeIn" :style="{animationDelay: (idx > 26) ? '160ms' : ((idx * 25) + 'ms')}"
|
||||||
ng-class="{selected: (image==selectedImage)}" ng-click="imageSelect(image)">
|
:class="{selected: (image==selectedImage)}" @click="imageSelect(image)">
|
||||||
<img ng-src="@{{image.thumbs.gallery}}" ng-attr-alt="@{{image.title}}" ng-attr-title="@{{image.name}}">
|
<img :src="image.thumbs.gallery" :alt="image.title" :title="image.name">
|
||||||
<div class="image-meta">
|
<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>
|
<span class="date">{{ trans('components.image_uploaded', ['uploadedDate' => "{{ getDate(image.created_at) }" . "}"]) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<div class="image-manager-sidebar">
|
<div class="image-manager-sidebar">
|
||||||
<div class="inner">
|
<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>
|
<div>
|
||||||
<a ng-href="@{{selectedImage.url}}" target="_blank" style="display: block;">
|
<a :href="selectedImage.url" target="_blank" style="display: block;">
|
||||||
<img ng-src="@{{selectedImage.thumbs.gallery}}" ng-attr-alt="@{{selectedImage.title}}" ng-attr-title="@{{selectedImage.name}}">
|
<img :src="selectedImage.thumbs.gallery" :alt="selectedImage.title"
|
||||||
|
:title="selectedImage.name">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">{{ trans('components.image_image_name') }}</label>
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div ng-show="dependantPages">
|
<div v-show="dependantPages">
|
||||||
<p class="text-neg text-small">
|
<p class="text-neg text-small">
|
||||||
{{ trans('components.image_delete_confirm') }}
|
{{ trans('components.image_delete_confirm') }}
|
||||||
</p>
|
</p>
|
||||||
<ul class="text-neg">
|
<ul class="text-neg">
|
||||||
<li ng-repeat="page in dependantPages">
|
<li v-for="page in dependantPages">
|
||||||
<a ng-href="@{{ page.url }}" target="_blank" class="text-neg" ng-bind="page.name"></a>
|
<a :href="page.url" target="_blank" class="text-neg" v-text="page.name"></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix">
|
<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>
|
<button class="button icon neg"><i class="zmdi zmdi-delete"></i></button>
|
||||||
</form>
|
</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') }}
|
<i class="zmdi zmdi-square-right"></i>{{ trans('components.image_select_image') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6 faded">
|
<div class="col-sm-6 faded">
|
||||||
<div class="action-buttons text-left">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@include('components.image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
|
@include('components.image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
|
||||||
|
@include('components.code-editor')
|
||||||
@include('components.entity-selector-popup')
|
@include('components.entity-selector-popup')
|
||||||
|
|
||||||
@stop
|
@stop
|
|
@ -9,31 +9,40 @@
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</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>
|
<h4>{{ trans('entities.page_tags') }}</h4>
|
||||||
<div class="padded tags">
|
<div class="padded tags">
|
||||||
<p class="muted small">{!! nl2br(e(trans('entities.tags_explain'))) !!}</p>
|
<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" >
|
<draggable class="fake-table no-style tag-table" :options="{handle: '.handle'}" :list="tags" element="div" style="width: 100%;">
|
||||||
<tr ng-repeat="tag in tags track by $index">
|
<transition-group name="test" tag="div">
|
||||||
<td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
|
<div v-for="(tag, i) in tags" :key="tag.key">
|
||||||
<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>
|
<div width="20" class="handle" ><i class="zmdi zmdi-menu"></i></div>
|
||||||
<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>
|
<div>
|
||||||
<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>
|
<autosuggest url="/ajax/tags/suggest/names" type="name" class="outline" :name="getTagFieldName(i, 'name')"
|
||||||
</tr>
|
v-model="tag.name" @input="tagChange(tag)" @blur="tagBlur(tag)" placeholder="{{ trans('entities.tag') }}"/>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
<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%;">
|
<table class="no-style" style="width: 100%;">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="unsortable">
|
<tr class="unsortable">
|
||||||
<td width="34"></td>
|
<td width="34"></td>
|
||||||
<td ng-click="addEmptyTag()">
|
<td @click="addEmptyTag">
|
||||||
<button type="button" class="text-button">{{ trans('entities.tags_add') }}</button>
|
<button type="button" class="text-button">{{ trans('entities.tags_add') }}</button>
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -81,15 +90,15 @@
|
||||||
<p class="muted small">{{ trans('entities.attachments_explain_link') }}</p>
|
<p class="muted small">{{ trans('entities.attachments_explain_link') }}</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="attachment-via-link">{{ trans('entities.attachments_link_name') }}</label>
|
<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>
|
<p class="small neg" ng-repeat="error in errors.link.name" ng-bind="error"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="attachment-via-link">{{ trans('entities.attachments_link_url') }}</label>
|
<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>
|
<p class="small neg" ng-repeat="error in errors.link.link" ng-bind="error"></p>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="button pos">{{ trans('entities.attach') }}</button>
|
<button class="button pos">{{ trans('entities.attach') }}</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -117,14 +126,14 @@
|
||||||
<div tab-content="link">
|
<div tab-content="link">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="attachment-link-edit">{{ trans('entities.attachments_link_url') }}</label>
|
<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>
|
<p class="small neg" ng-repeat="error in errors.edit.link" ng-bind="error"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="button" ng-click="cancelEdit()">{{ trans('common.back') }}</button>
|
<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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,27 @@
|
||||||
<div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}">
|
<div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}">
|
||||||
<h4>
|
<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>
|
<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>
|
</h4>
|
||||||
|
|
||||||
@if(isset($page->searchSnippet))
|
<div class="entity-item-snippet">
|
||||||
<p class="text-muted">{!! $page->searchSnippet !!}</p>
|
@if(isset($page->searchSnippet))
|
||||||
@else
|
<p class="text-muted">{!! $page->searchSnippet !!}</p>
|
||||||
<p class="text-muted">{{ $page->getExcerpt() }}</p>
|
@else
|
||||||
@endif
|
<p class="text-muted">{{ $page->getExcerpt() }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
@if(isset($style) && $style === 'detailed')
|
@if(isset($style) && $style === 'detailed')
|
||||||
<div class="row meta text-muted text-small">
|
<div class="row meta text-muted text-small">
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
<div class="faded-small toolbar">
|
<div class="faded-small toolbar">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6 faded">
|
<div class="col-sm-8 col-xs-5 faded">
|
||||||
@include('pages._breadcrumbs', ['page' => $page])
|
@include('pages._breadcrumbs', ['page' => $page])
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 faded">
|
<div class="col-sm-4 col-xs-7 faded">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<span dropdown class="dropdown-container">
|
<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>
|
<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>
|
</a>
|
||||||
|
|
||||||
@if($bookChild->isA('chapter') && count($bookChild->pages) > 0)
|
@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>
|
<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>
|
</p>
|
||||||
<ul class="menu sub-menu inset-list @if($bookChild->matchesOrContains($current)) open @endif">
|
<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') }}">
|
<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;
|
background-color: {{ setting('app-color') }} !important;
|
||||||
}
|
}
|
||||||
.faded-small, .primary-background-light {
|
.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>
|
<i class="zmdi zmdi-check-circle"></i> <span>{!! nl2br(htmlentities(session()->get('success'))) !!}</span>
|
||||||
</div>
|
</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>
|
<i class="zmdi zmdi-info"></i> <span>{!! nl2br(htmlentities(session()->get('warning'))) !!}</span>
|
||||||
</div>
|
</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>
|
<i class="zmdi zmdi-alert-circle"></i> <span>{!! nl2br(htmlentities(session()->get('error'))) !!}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
@if(count($entities) > 0)
|
@if(count($entities) > 0)
|
||||||
@foreach($entities as $index => $entity)
|
@foreach($entities as $index => $entity)
|
||||||
@if($entity->isA('page'))
|
@if($entity->isA('page'))
|
||||||
@include('pages/list-item', ['page' => $entity])
|
@include('pages/list-item', ['page' => $entity, 'showPath' => true])
|
||||||
@elseif($entity->isA('book'))
|
@elseif($entity->isA('book'))
|
||||||
@include('books/list-item', ['book' => $entity])
|
@include('books/list-item', ['book' => $entity])
|
||||||
@elseif($entity->isA('chapter'))
|
@elseif($entity->isA('chapter'))
|
||||||
|
|
|
@ -11,6 +11,7 @@ class LdapTest extends BrowserKitTest
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
parent::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']);
|
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->mockLdap = \Mockery::mock(\BookStack\Services\Ldap::class);
|
||||||
$this->app['BookStack\Services\Ldap'] = $this->mockLdap;
|
$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('connect')->once()->andReturn($this->resourceId);
|
||||||
$this->mockLdap->shouldReceive('setVersion')->once();
|
$this->mockLdap->shouldReceive('setVersion')->once();
|
||||||
|
$this->mockLdap->shouldReceive('setOption')->times(4);
|
||||||
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
|
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
|
||||||
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
|
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
|
||||||
->andReturn(['count' => 1, 0 => [
|
->andReturn(['count' => 1, 0 => [
|
||||||
|
@ -49,6 +51,7 @@ class LdapTest extends BrowserKitTest
|
||||||
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
|
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
|
||||||
$this->mockLdap->shouldReceive('setVersion')->once();
|
$this->mockLdap->shouldReceive('setVersion')->once();
|
||||||
$ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
|
$ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
|
||||||
|
$this->mockLdap->shouldReceive('setOption')->times(2);
|
||||||
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
|
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
|
||||||
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
|
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
|
||||||
->andReturn(['count' => 1, 0 => [
|
->andReturn(['count' => 1, 0 => [
|
||||||
|
@ -72,6 +75,7 @@ class LdapTest extends BrowserKitTest
|
||||||
{
|
{
|
||||||
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
|
$this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
|
||||||
$this->mockLdap->shouldReceive('setVersion')->once();
|
$this->mockLdap->shouldReceive('setVersion')->once();
|
||||||
|
$this->mockLdap->shouldReceive('setOption')->times(2);
|
||||||
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
|
$this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
|
||||||
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
|
->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
|
||||||
->andReturn(['count' => 1, 0 => [
|
->andReturn(['count' => 1, 0 => [
|
||||||
|
@ -129,4 +133,4 @@ class LdapTest extends BrowserKitTest
|
||||||
->dontSee('External Authentication');
|
->dontSee('External Authentication');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?php namespace Tests;
|
<?php namespace Tests;
|
||||||
|
|
||||||
|
use BookStack\Entity;
|
||||||
use BookStack\Role;
|
use BookStack\Role;
|
||||||
use BookStack\Services\PermissionService;
|
use BookStack\Services\PermissionService;
|
||||||
use Illuminate\Contracts\Console\Kernel;
|
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
|
* Quick way to create a new user
|
||||||
* @param array $attributes
|
* @param array $attributes
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<?php namespace Tests;
|
<?php namespace Tests;
|
||||||
|
|
||||||
|
|
||||||
|
use BookStack\Chapter;
|
||||||
|
use BookStack\Page;
|
||||||
|
|
||||||
class EntitySearchTest extends TestCase
|
class EntitySearchTest extends TestCase
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -75,10 +78,10 @@ class EntitySearchTest extends TestCase
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
|
|
||||||
$pageA = \BookStack\Page::first();
|
$pageA = Page::first();
|
||||||
$pageA->tags()->saveMany($newTags);
|
$pageA->tags()->saveMany($newTags);
|
||||||
|
|
||||||
$pageB = \BookStack\Page::all()->last();
|
$pageB = Page::all()->last();
|
||||||
$pageB->tags()->create(['name' => 'animal', 'value' => 'dog']);
|
$pageB->tags()->create(['name' => 'animal', 'value' => 'dog']);
|
||||||
|
|
||||||
$this->asEditor();
|
$this->asEditor();
|
||||||
|
@ -160,8 +163,8 @@ class EntitySearchTest extends TestCase
|
||||||
|
|
||||||
public function test_ajax_entity_search()
|
public function test_ajax_entity_search()
|
||||||
{
|
{
|
||||||
$page = \BookStack\Page::all()->last();
|
$page = Page::all()->last();
|
||||||
$notVisitedPage = \BookStack\Page::first();
|
$notVisitedPage = Page::first();
|
||||||
|
|
||||||
// Visit the page to make popular
|
// Visit the page to make popular
|
||||||
$this->asEditor()->get($page->getUrl());
|
$this->asEditor()->get($page->getUrl());
|
||||||
|
@ -176,4 +179,20 @@ class EntitySearchTest extends TestCase
|
||||||
$defaultListTest->assertSee($page->name);
|
$defaultListTest->assertSee($page->name);
|
||||||
$defaultListTest->assertDontSee($notVisitedPage->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;
|
<?php namespace Tests;
|
||||||
|
|
||||||
|
use BookStack\Page;
|
||||||
use BookStack\Repos\PermissionsRepo;
|
use BookStack\Repos\PermissionsRepo;
|
||||||
use BookStack\Role;
|
use BookStack\Role;
|
||||||
|
use Laravel\BrowserKitTesting\HttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
class RolesTest extends BrowserKitTest
|
class RolesTest extends BrowserKitTest
|
||||||
{
|
{
|
||||||
|
@ -580,8 +583,6 @@ class RolesTest extends BrowserKitTest
|
||||||
->see('Cannot be deleted');
|
->see('Cannot be deleted');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function test_image_delete_own_permission()
|
public function test_image_delete_own_permission()
|
||||||
{
|
{
|
||||||
$this->giveUserPermissions($this->user, ['image-update-all']);
|
$this->giveUserPermissions($this->user, ['image-update-all']);
|
||||||
|
@ -620,6 +621,42 @@ class RolesTest extends BrowserKitTest
|
||||||
->dontSeeInDatabase('images', ['id' => $image->id]);
|
->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 () {
|
public function test_comment_create_permission () {
|
||||||
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
|
$ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue