]> BookStack Code Mirror - bookstack/commitdiff
Merge branch 'master' of git://github.com/Abijeet/BookStack into Abijeet-master
authorDan Brown <redacted>
Tue, 1 Aug 2017 18:24:33 +0000 (19:24 +0100)
committerDan Brown <redacted>
Tue, 1 Aug 2017 18:24:33 +0000 (19:24 +0100)
68 files changed:
.travis.yml
app/Console/Commands/UpgradeDatabaseEncoding.php [new file with mode: 0644]
app/Console/Kernel.php
app/Providers/AppServiceProvider.php
app/Repos/EntityRepo.php
app/Services/LdapService.php
app/Services/PermissionService.php
config/app.php
config/database.php
config/services.php
database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php [new file with mode: 0644]
gulpfile.js
package.json
readme.md
resources/assets/js/code.js
resources/assets/js/controllers.js
resources/assets/js/directives.js
resources/assets/js/global.js
resources/assets/js/pages/page-form.js
resources/assets/js/vues/code-editor.js [new file with mode: 0644]
resources/assets/js/vues/search.js
resources/assets/js/vues/vues.js
resources/assets/sass/_codemirror.scss
resources/assets/sass/_components.scss
resources/assets/sass/_forms.scss
resources/assets/sass/_header.scss
resources/assets/sass/_text.scss
resources/lang/en/components.php
resources/lang/en/settings.php
resources/lang/fr/auth.php
resources/lang/fr/common.php
resources/lang/fr/entities.php
resources/lang/fr/errors.php
resources/lang/fr/passwords.php
resources/lang/fr/settings.php
resources/lang/ja/activities.php [new file with mode: 0644]
resources/lang/ja/auth.php [new file with mode: 0644]
resources/lang/ja/common.php [new file with mode: 0644]
resources/lang/ja/components.php [new file with mode: 0644]
resources/lang/ja/entities.php [new file with mode: 0644]
resources/lang/ja/errors.php [new file with mode: 0644]
resources/lang/ja/pagination.php [new file with mode: 0644]
resources/lang/ja/passwords.php [new file with mode: 0644]
resources/lang/ja/settings.php [new file with mode: 0644]
resources/lang/ja/validation.php [new file with mode: 0644]
resources/lang/pl/activities.php [new file with mode: 0644]
resources/lang/pl/auth.php [new file with mode: 0644]
resources/lang/pl/common.php [new file with mode: 0644]
resources/lang/pl/components.php [new file with mode: 0644]
resources/lang/pl/entities.php [new file with mode: 0644]
resources/lang/pl/errors.php [new file with mode: 0644]
resources/lang/pl/pagination.php [new file with mode: 0644]
resources/lang/pl/passwords.php [new file with mode: 0644]
resources/lang/pl/settings.php [new file with mode: 0644]
resources/lang/pl/validation.php [new file with mode: 0644]
resources/views/books/show.blade.php
resources/views/chapters/list-item.blade.php
resources/views/chapters/show.blade.php
resources/views/components/code-editor.blade.php [new file with mode: 0644]
resources/views/pages/edit.blade.php
resources/views/pages/list-item.blade.php
resources/views/pages/show.blade.php
resources/views/search/entity-ajax-list.blade.php
tests/Auth/LdapTest.php
tests/BrowserKitTest.php
tests/Entity/EntitySearchTest.php
tests/Permissions/RolesTest.php
version

index 909e3e1f4a6c3f23bb08a4ce8d71b80925487646..839d3be3f951a83f0f0b3e35855c069ca6bce232 100644 (file)
@@ -25,4 +25,4 @@ after_failure:
   - cat storage/logs/laravel.log
 
 script:
-  - phpunit
\ No newline at end of file
+  - phpunit
diff --git a/app/Console/Commands/UpgradeDatabaseEncoding.php b/app/Console/Commands/UpgradeDatabaseEncoding.php
new file mode 100644 (file)
index 0000000..a17fc95
--- /dev/null
@@ -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);
+    }
+}
index 4fa0b3c80c682544a4a59eac88211d5ec23c40b2..af9f5fd4617e6a765251a066aeb4552721863ed9 100644 (file)
@@ -15,7 +15,8 @@ class Kernel extends ConsoleKernel
         Commands\ClearActivity::class,
         Commands\ClearRevisions::class,
         Commands\RegeneratePermissions::class,
-        Commands\RegenerateSearch::class
+        Commands\RegenerateSearch::class,
+        Commands\UpgradeDatabaseEncoding::class
     ];
 
     /**
index 49cc15dd6130920b3a5d5a6c028ae6d3bbfd1bd9..ef3ee6c48c790438fc6dc42c77332f6638ecfad1 100644 (file)
@@ -23,6 +23,9 @@ class AppServiceProvider extends ServiceProvider
         \Blade::directive('icon', function($expression) {
             return "<?php echo icon($expression); ?>";
         });
+
+        // Allow longer string lengths after upgrade to utf8mb4
+        \Schema::defaultStringLength(191);
     }
 
     /**
index 7bc5fc4fc159f2a2984e8b2fe62eda5593416b44..d87c40f9b12cca06c74fbe81b5133e84c6a7c517 100644 (file)
@@ -571,7 +571,7 @@ class EntityRepo
 
         $draftPage->slug = $this->findSuitableSlug('page', $draftPage->name, false, $draftPage->book->id);
         $draftPage->html = $this->formatHtml($input['html']);
-        $draftPage->text = strip_tags($draftPage->html);
+        $draftPage->text = $this->pageToPlainText($draftPage);
         $draftPage->draft = false;
         $draftPage->revision_count = 1;
 
@@ -713,6 +713,17 @@ class EntityRepo
         return $content;
     }
 
+    /**
+     * Get the plain text version of a page's content.
+     * @param Page $page
+     * @return string
+     */
+    public function pageToPlainText(Page $page)
+    {
+        $html = $this->renderPage($page);
+        return strip_tags($html);
+    }
+
     /**
      * Get a new draft page instance.
      * @param Book $book
@@ -816,7 +827,7 @@ class EntityRepo
         $userId = user()->id;
         $page->fill($input);
         $page->html = $this->formatHtml($input['html']);
-        $page->text = strip_tags($page->html);
+        $page->text = $this->pageToPlainText($page);
         if (setting('app-editor') !== 'markdown') $page->markdown = '';
         $page->updated_by = $userId;
         $page->revision_count++;
@@ -933,7 +944,7 @@ class EntityRepo
         $revision = $page->revisions()->where('id', '=', $revisionId)->first();
         $page->fill($revision->toArray());
         $page->slug = $this->findSuitableSlug('page', $page->name, $page->id, $book->id);
-        $page->text = strip_tags($page->html);
+        $page->text = $this->pageToPlainText($page);
         $page->updated_by = user()->id;
         $page->save();
         $this->searchService->indexEntity($page);
@@ -953,7 +964,7 @@ class EntityRepo
         if ($page->draft) {
             $page->fill($data);
             if (isset($data['html'])) {
-                $page->text = strip_tags($data['html']);
+                $page->text = $this->pageToPlainText($page);
             }
             $page->save();
             return $page;
index 71dc9c0e1a57b731048445bcdd78fed57e98c6a2..598efc19dd68828fab45da38304509056f215189 100644 (file)
@@ -42,6 +42,8 @@ class LdapService
         $userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]);
         $baseDn = $this->config['base_dn'];
         $emailAttr = $this->config['email_attribute'];
+        $followReferrals = $this->config['follow_referrals'] ? 1 : 0;
+        $this->ldap->setOption($ldapConnection, LDAP_OPT_REFERRALS, $followReferrals);
         $users = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $userFilter, ['cn', 'uid', 'dn', $emailAttr]);
         if ($users['count'] === 0) return null;
 
index 89f80f9360a878f6224a36853a399c691221a6a1..93787a3e589ba9e6a1d82d2be5030af6ded82c13 100644 (file)
@@ -259,7 +259,7 @@ class PermissionService
         $roleIds = array_map(function($role) {
             return $role->id;
         }, $roles);
-        $this->jointPermission->newQuery()->whereIn('id', $roleIds)->delete();
+        $this->jointPermission->newQuery()->whereIn('role_id', $roleIds)->delete();
     }
 
     /**
index 54cdca21bb25893d8c5b85aa4bc78a5eb10aa891..a390eaf83a16eb20360f47e30be0d6090ad73eea 100644 (file)
@@ -58,7 +58,7 @@ return [
     */
 
     'locale' => env('APP_LANG', 'en'),
-    'locales' => ['en', 'de', 'es', 'fr', 'nl', 'pt_BR', 'sk'],
+    'locales' => ['en', 'de', 'es', 'fr', 'nl', 'pt_BR', 'sk', 'ja', 'pl'],
 
     /*
     |--------------------------------------------------------------------------
index 92c7682450a68317015e6559380e34cfeaf414db..3883b58686ae4abb54c26b7dc94578be236e6c6f 100644 (file)
@@ -16,6 +16,14 @@ if (env('REDIS_SERVERS', false)) {
     }
 }
 
+$mysql_host = env('DB_HOST', 'localhost');
+$mysql_host_exploded = explode(':', $mysql_host);
+$mysql_port = env('DB_PORT', 3306);
+if (count($mysql_host_exploded) > 1) {
+    $mysql_host = $mysql_host_exploded[0];
+    $mysql_port = intval($mysql_host_exploded[1]);
+}
+
 return [
 
     /*
@@ -70,12 +78,13 @@ return [
 
         'mysql' => [
             'driver'    => 'mysql',
-            'host'      => env('DB_HOST', 'localhost'),
+            'host'      => $mysql_host,
             'database'  => env('DB_DATABASE', 'forge'),
             'username'  => env('DB_USERNAME', 'forge'),
             'password'  => env('DB_PASSWORD', ''),
-            'charset'   => 'utf8',
-            'collation' => 'utf8_unicode_ci',
+            'port'      => $mysql_port,
+            'charset'   => 'utf8mb4',
+            'collation' => 'utf8mb4_unicode_ci',
             'prefix'    => '',
             'strict'    => false,
         ],
index 99022e5f2bed0b0b7a415a9d6929ef9f1c45bf3a..b4959c7249cbbffb0024bc448e8ec00bb452172a 100644 (file)
@@ -80,6 +80,7 @@ return [
         'user_filter' => env('LDAP_USER_FILTER', '(&(uid=${user}))'),
         'version' => env('LDAP_VERSION', false),
         'email_attribute' => env('LDAP_EMAIL_ATTRIBUTE', 'mail'),
+        'follow_referrals' => env('LDAP_FOLLOW_REFERRALS', false),
     ]
 
 ];
diff --git a/database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php b/database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php
new file mode 100644 (file)
index 0000000..5681013
--- /dev/null
@@ -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()
+    {
+        //
+    }
+}
index 580db00cc0d6cbb5d9ac923fc280050175132520..f851dd7d626753cf1c6623de50be48000e91fcff 100644 (file)
@@ -14,8 +14,10 @@ const babelify = require("babelify");
 const watchify = require("watchify");
 const envify = require("envify");
 const gutil = require("gulp-util");
+const liveReload = require('gulp-livereload');
 
 if (argv.production) process.env.NODE_ENV = 'production';
+let isProduction = argv.production || process.env.NODE_ENV === 'production';
 
 gulp.task('styles', () => {
     let chain = gulp.src(['resources/assets/sass/**/*.scss'])
@@ -26,31 +28,40 @@ gulp.task('styles', () => {
             }}))
         .pipe(sass())
         .pipe(autoprefixer('last 2 versions'));
-    if (argv.production) chain = chain.pipe(minifycss());
-    return chain.pipe(gulp.dest('public/css/'));
+    if (isProduction) chain = chain.pipe(minifycss());
+    return chain.pipe(gulp.dest('public/css/')).pipe(liveReload());
 });
 
 
-function scriptTask(watch=false) {
+function scriptTask(watch = false) {
 
     let props = {
         basedir: 'resources/assets/js',
         debug: true,
-        entries: ['global.js']
+        entries: ['global.js'],
+        fast: !isProduction,
+        cache: {},
+        packageCache: {},
     };
 
     let bundler = watch ? watchify(browserify(props), { poll: true }) : browserify(props);
-    bundler.transform(envify, {global: true}).transform(babelify, {presets: ['es2015']});
+
+    if (isProduction) {
+        bundler.transform(envify, {global: true}).transform(babelify, {presets: ['es2015']});
+    }
+
     function rebundle() {
         let stream = bundler.bundle();
         stream = stream.pipe(source('common.js'));
-        if (argv.production) stream = stream.pipe(buffer()).pipe(uglify());
-        return stream.pipe(gulp.dest('public/js/'));
+        if (isProduction) stream = stream.pipe(buffer()).pipe(uglify());
+        return stream.pipe(gulp.dest('public/js/')).pipe(liveReload());
     }
+
     bundler.on('update', function() {
         rebundle();
-        gutil.log('Rebundle...');
+        gutil.log('Rebundling assets...');
     });
+
     bundler.on('log', gutil.log);
     return rebundle();
 }
@@ -59,6 +70,7 @@ gulp.task('scripts', () => {scriptTask(false)});
 gulp.task('scripts-watch', () => {scriptTask(true)});
 
 gulp.task('default', ['styles', 'scripts-watch'], () => {
+    liveReload.listen();
     gulp.watch("resources/assets/sass/**/*.scss", ['styles']);
 });
 
index 93f62bf1f5e0dcf8a05d9a582bd8d13195f585c2..429572882c46fdb1f8fd81adfe680c0090abf9a4 100644 (file)
@@ -4,7 +4,8 @@
     "build": "gulp build",
     "production": "gulp build --production",
     "dev": "gulp",
-    "watch": "gulp"
+    "watch": "gulp",
+    "permissions": "chown -R $USER:$USER bootstrap/cache storage public/uploads"
   },
   "devDependencies": {
     "babelify": "^7.3.0",
@@ -13,6 +14,7 @@
     "gulp": "3.9.1",
     "gulp-autoprefixer": "3.1.1",
     "gulp-clean-css": "^3.0.4",
+    "gulp-livereload": "^3.8.1",
     "gulp-minify-css": "1.2.4",
     "gulp-plumber": "1.1.0",
     "gulp-sass": "3.1.0",
index e2f16e171bc15517e35ba35ee6b1687075ae3dcf..6067fbb945b954341095106432d71202413d0ead 100644 (file)
--- a/readme.md
+++ b/readme.md
@@ -22,9 +22,12 @@ All development on BookStack is currently done on the master branch. When it's t
 SASS is used to help the CSS development and the JavaScript is run through browserify/babel to allow for writing ES6 code. Both of these are done using gulp. To run the build task you can use the following commands:
 
 ``` bash
-# Build and minify for production
+# Build assets for development
 npm run-script build
 
+# Build and minify assets for production
+npm run-script production
+
 # Build for dev (With sourcemaps) and watch for changes
 npm run-script dev
 ```
@@ -64,13 +67,15 @@ The BookStack source is provided under the MIT License.
 
 ## Attribution
 
-These are the great projects used to help build BookStack:
+These are the great open-source projects used to help build BookStack:
 
 * [Laravel](http://laravel.com/)
 * [AngularJS](https://angularjs.org/)
 * [jQuery](https://jquery.com/)
 * [TinyMCE](https://www.tinymce.com/)
-* [highlight.js](https://highlightjs.org/)
+* [CodeMirror](https://codemirror.net)
+* [Vue.js](http://vuejs.org/)
+* [Axios](https://github.com/mzabriskie/axios)
 * [jQuery Sortable](https://johnny.github.io/jquery-sortable/)
 * [Material Design Iconic Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html)
 * [Dropzone.js](http://www.dropzonejs.com/)
@@ -84,5 +89,3 @@ These are the great projects used to help build BookStack:
     * [Snappy (WKHTML2PDF)](https://github.com/barryvdh/laravel-snappy)
     * [Laravel IDE helper](https://github.com/barryvdh/laravel-ide-helper)
 * [WKHTMLtoPDF](http://wkhtmltopdf.org/index.html)
-
-Additionally, Thank you [BrowserStack](https://www.browserstack.com/) for supporting us and making cross-browser testing easy.
index 872b1342658995d0f68358aacf4383f4e6435cb7..014c4eb779674fc1234bd9b07aec90618e4afc10 100644 (file)
@@ -1,5 +1,6 @@
 require('codemirror/mode/css/css');
 require('codemirror/mode/clike/clike');
+require('codemirror/mode/diff/diff');
 require('codemirror/mode/go/go');
 require('codemirror/mode/htmlmixed/htmlmixed');
 require('codemirror/mode/javascript/javascript');
@@ -17,40 +18,161 @@ require('codemirror/mode/yaml/yaml');
 
 const CodeMirror = require('codemirror');
 
+const modeMap = {
+    css: 'css',
+    c: 'clike',
+    java: 'clike',
+    scala: 'clike',
+    kotlin: 'clike',
+    'c++': 'clike',
+    'c#': 'clike',
+    csharp: 'clike',
+    diff: 'diff',
+    go: 'go',
+    html: 'htmlmixed',
+    javascript: 'javascript',
+    json: {name: 'javascript', json: true},
+    js: 'javascript',
+    php: 'php',
+    md: 'markdown',
+    mdown: 'markdown',
+    markdown: 'markdown',
+    nginx: 'nginx',
+    powershell: 'powershell',
+    py: 'python',
+    python: 'python',
+    ruby: 'ruby',
+    rb: 'ruby',
+    shell: 'shell',
+    sh: 'shell',
+    bash: 'shell',
+    toml: 'toml',
+    sql: 'sql',
+    xml: 'xml',
+    yaml: 'yaml',
+    yml: 'yaml',
+};
+
 module.exports.highlight = function() {
     let codeBlocks = document.querySelectorAll('.page-content pre');
-
     for (let i = 0; i < codeBlocks.length; i++) {
-        codeBlocks[i].innerHTML = codeBlocks[i].innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
-        let content = codeBlocks[i].textContent;
-
-        CodeMirror(function(elt) {
-            codeBlocks[i].parentNode.replaceChild(elt, codeBlocks[i]);
-        }, {
-            value: content,
-            mode:  "",
-            lineNumbers: true,
-            theme: 'base16-light',
-            readOnly: true
-        });
+        highlightElem(codeBlocks[i]);
+    }
+};
+
+function highlightElem(elem) {
+    let innerCodeElem = elem.querySelector('code[class^=language-]');
+    let mode = '';
+    if (innerCodeElem !== null) {
+        let langName = innerCodeElem.className.replace('language-', '');
+        mode = getMode(langName);
     }
+    elem.innerHTML = elem.innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
+    let content = elem.textContent;
 
+    CodeMirror(function(elt) {
+        elem.parentNode.replaceChild(elt, elem);
+    }, {
+        value: content,
+        mode:  mode,
+        lineNumbers: true,
+        theme: 'base16-light',
+        readOnly: true
+    });
+}
+
+/**
+ * Search for a codemirror code based off a user suggestion
+ * @param suggestion
+ * @returns {string}
+ */
+function getMode(suggestion) {
+    suggestion = suggestion.trim().replace(/^\./g, '').toLowerCase();
+    return (typeof modeMap[suggestion] !== 'undefined') ? modeMap[suggestion] : '';
+}
+
+module.exports.highlightElem = highlightElem;
+
+module.exports.wysiwygView = function(elem) {
+    let doc = elem.ownerDocument;
+    let codeElem = elem.querySelector('code');
+
+    let lang = (elem.className || '').replace('language-', '');
+    if (lang === '' && codeElem) {
+        lang = (codeElem.className || '').replace('language-', '')
+    }
+
+    elem.innerHTML = elem.innerHTML.replace(/<br\s*[\/]?>/gi ,'\n');
+    let content = elem.textContent;
+    let newWrap = doc.createElement('div');
+    let newTextArea = doc.createElement('textarea');
+
+    newWrap.className = 'CodeMirrorContainer';
+    newWrap.setAttribute('data-lang', lang);
+    newTextArea.style.display = 'none';
+    elem.parentNode.replaceChild(newWrap, elem);
+
+    newWrap.appendChild(newTextArea);
+    newWrap.contentEditable = false;
+    newTextArea.textContent = content;
+
+    let cm = CodeMirror(function(elt) {
+        newWrap.appendChild(elt);
+    }, {
+        value: content,
+        mode:  getMode(lang),
+        lineNumbers: true,
+        theme: 'base16-light',
+        readOnly: true
+    });
+    setTimeout(() => {
+        cm.refresh();
+    }, 300);
+    return {wrap: newWrap, editor: cm};
+};
+
+module.exports.popupEditor = function(elem, modeSuggestion) {
+    let content = elem.textContent;
+
+    return CodeMirror(function(elt) {
+        elem.parentNode.insertBefore(elt, elem);
+        elem.style.display = 'none';
+    }, {
+        value: content,
+        mode:  getMode(modeSuggestion),
+        lineNumbers: true,
+        theme: 'base16-light',
+        lineWrapping: true
+    });
+};
+
+module.exports.setMode = function(cmInstance, modeSuggestion) {
+      cmInstance.setOption('mode', getMode(modeSuggestion));
+};
+module.exports.setContent = function(cmInstance, codeContent) {
+    cmInstance.setValue(codeContent);
+    setTimeout(() => {
+        cmInstance.refresh();
+    }, 10);
 };
 
 module.exports.markdownEditor = function(elem) {
     let content = elem.textContent;
 
-    let cm = CodeMirror(function(elt) {
+    return CodeMirror(function (elt) {
         elem.parentNode.insertBefore(elt, elem);
         elem.style.display = 'none';
     }, {
         value: content,
-        mode:  "markdown",
+        mode: "markdown",
         lineNumbers: true,
         theme: 'base16-light',
         lineWrapping: true
     });
-    return cm;
+};
 
+module.exports.getMetaKey = function() {
+    let mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
+    return mac ? "Cmd" : "Ctrl";
 };
 
index aebde8da4136fcd7bc2f293e7198742661fa90bd..e1d838bb6375a610c435b05e2c9d097493ece887 100644 (file)
@@ -370,14 +370,8 @@ module.exports = function (ngApp, events) {
             saveDraft();
         };
 
-        // Listen to shortcuts coming via events
-        $scope.$on('editor-keydown', (event, data) => {
-            // Save shortcut (ctrl+s)
-            if (data.keyCode == 83 && (navigator.platform.match("Mac") ? data.metaKey : data.ctrlKey)) {
-                data.preventDefault();
-                saveDraft();
-            }
-        });
+        // Listen to save draft events from editor
+        $scope.$on('save-draft', saveDraft);
 
         /**
          * Discard the current draft and grab the current page
@@ -385,7 +379,7 @@ module.exports = function (ngApp, events) {
          */
         $scope.discardDraft = function () {
             let url = window.baseUrl('/ajax/page/' + pageId);
-            $http.get(url).then((responseData) => {
+            $http.get(url).then(responseData => {
                 if (autoSave) $interval.cancel(autoSave);
                 $scope.draftText = trans('entities.pages_editing_page');
                 $scope.isUpdateDraft = false;
index 16d1ad2a4fe1be758f57cc24ac0a2b286275b509..d8745462d03f4c5964f699d5b6b18276e2dc1be4 100644 (file)
@@ -123,25 +123,31 @@ module.exports = function (ngApp, events) {
             restrict: 'A',
             link: function (scope, element, attrs) {
                 const menu = element.find('ul');
-                element.find('[dropdown-toggle]').on('click', function () {
+
+                function hide() {
+                    menu.hide();
+                    menu.removeClass('anim menuIn');
+                }
+
+                function show() {
                     menu.show().addClass('anim menuIn');
+                    element.mouseleave(hide);
+
+                    // Focus on input if exist in dropdown and hide on enter press
                     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');
-                    });
+                    if (inputs.length > 0) inputs.first().focus();
+                }
+
+                // Hide menu on option click
+                element.on('click', '> ul a', hide);
+                // Show dropdown on toggle click.
+                element.find('[dropdown-toggle]').on('click', show);
+                // Hide menu on enter press in inputs
+                element.on('keypress', 'input', event => {
+                    if (event.keyCode !== 13) return true;
+                    event.preventDefault();
+                    hide();
+                    return false;
                 });
             }
         };
@@ -187,30 +193,6 @@ module.exports = function (ngApp, events) {
                 }
 
                 scope.tinymce.extraSetups.push(tinyMceSetup);
-
-                // Custom tinyMCE plugins
-                tinymce.PluginManager.add('customhr', function (editor) {
-                    editor.addCommand('InsertHorizontalRule', function () {
-                        let hrElem = document.createElement('hr');
-                        let cNode = editor.selection.getNode();
-                        let parentNode = cNode.parentNode;
-                        parentNode.insertBefore(hrElem, cNode);
-                    });
-
-                    editor.addButton('hr', {
-                        icon: 'hr',
-                        tooltip: 'Horizontal line',
-                        cmd: 'InsertHorizontalRule'
-                    });
-
-                    editor.addMenuItem('hr', {
-                        icon: 'hr',
-                        text: 'Horizontal line',
-                        cmd: 'InsertHorizontalRule',
-                        context: 'insert'
-                    });
-                });
-
                 tinymce.init(scope.tinymce);
             }
         }
@@ -232,15 +214,48 @@ module.exports = function (ngApp, events) {
             },
             link: function (scope, element, attrs) {
 
-                // Set initial model content
-                element = element.find('textarea').first();
-
                 // Codemirror Setup
+                element = element.find('textarea').first();
                 let cm = code.markdownEditor(element[0]);
+
+                // Custom key commands
+                let metaKey = code.getMetaKey();
+                const extraKeys = {};
+                // Insert Image shortcut
+                extraKeys[`${metaKey}-Alt-I`] = function(cm) {
+                    let selectedText = cm.getSelection();
+                    let newText = `![${selectedText}](http://)`;
+                    let cursorPos = cm.getCursor('from');
+                    cm.replaceSelection(newText);
+                    cm.setCursor(cursorPos.line, cursorPos.ch + newText.length -1);
+                };
+                // Save draft
+                extraKeys[`${metaKey}-S`] = function(cm) {scope.$emit('save-draft');};
+                // Show link selector
+                extraKeys[`Shift-${metaKey}-K`] = function(cm) {showLinkSelector()};
+                // Insert Link
+                extraKeys[`${metaKey}-K`] = function(cm) {insertLink()};
+                // FormatShortcuts
+                extraKeys[`${metaKey}-1`] = function(cm) {replaceLineStart('##');};
+                extraKeys[`${metaKey}-2`] = function(cm) {replaceLineStart('###');};
+                extraKeys[`${metaKey}-3`] = function(cm) {replaceLineStart('####');};
+                extraKeys[`${metaKey}-4`] = function(cm) {replaceLineStart('#####');};
+                extraKeys[`${metaKey}-5`] = function(cm) {replaceLineStart('');};
+                extraKeys[`${metaKey}-d`] = function(cm) {replaceLineStart('');};
+                extraKeys[`${metaKey}-6`] = function(cm) {replaceLineStart('>');};
+                extraKeys[`${metaKey}-q`] = function(cm) {replaceLineStart('>');};
+                extraKeys[`${metaKey}-7`] = function(cm) {wrapSelection('\n```\n', '\n```');};
+                extraKeys[`${metaKey}-8`] = function(cm) {wrapSelection('`', '`');};
+                extraKeys[`Shift-${metaKey}-E`] = function(cm) {wrapSelection('`', '`');};
+                extraKeys[`${metaKey}-9`] = function(cm) {wrapSelection('<p class="callout info">', '</div>');};
+                cm.setOption('extraKeys', extraKeys);
+
+                // Update data on content change
                 cm.on('change', (instance, changeObj) => {
                     update(instance);
                 });
 
+                // Handle scroll to sync display view
                 cm.on('scroll', instance => {
                     // Thanks to http://liuhao.im/english/2015/11/10/the-sync-scroll-of-markdown-editor-in-javascript.html
                     let scroll = instance.getScrollInfo();
@@ -257,6 +272,166 @@ module.exports = function (ngApp, events) {
                     scope.$emit('markdown-scroll', totalLines.length);
                 });
 
+                // Handle image paste
+                cm.on('paste', (cm, event) => {
+                    if (!event.clipboardData || !event.clipboardData.items) return;
+                    for (let i = 0; i < event.clipboardData.items.length; i++) {
+                        uploadImage(event.clipboardData.items[i].getAsFile());
+                    }
+                });
+
+                // Handle images on drag-drop
+                cm.on('drop', (cm, event) => {
+                    event.stopPropagation();
+                    event.preventDefault();
+                    let cursorPos = cm.coordsChar({left: event.pageX, top: event.pageY});
+                    cm.setCursor(cursorPos);
+                    if (!event.dataTransfer || !event.dataTransfer.files) return;
+                    for (let i = 0; i < event.dataTransfer.files.length; i++) {
+                        uploadImage(event.dataTransfer.files[i]);
+                    }
+                });
+
+                // Helper to replace editor content
+                function replaceContent(search, replace) {
+                    let text = cm.getValue();
+                    let cursor = cm.listSelections();
+                    cm.setValue(text.replace(search, replace));
+                    cm.setSelections(cursor);
+                }
+
+                // Helper to replace the start of the line
+                function replaceLineStart(newStart) {
+                    let cursor = cm.getCursor();
+                    let lineContent = cm.getLine(cursor.line);
+                    let lineLen = lineContent.length;
+                    let lineStart = lineContent.split(' ')[0];
+
+                    // Remove symbol if already set
+                    if (lineStart === newStart) {
+                        lineContent = lineContent.replace(`${newStart} `, '');
+                        cm.replaceRange(lineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen});
+                        cm.setCursor({line: cursor.line, ch: cursor.ch - (newStart.length + 1)});
+                        return;
+                    }
+
+                    let alreadySymbol = /^[#>`]/.test(lineStart);
+                    let posDif = 0;
+                    if (alreadySymbol) {
+                        posDif = newStart.length - lineStart.length;
+                        lineContent = lineContent.replace(lineStart, newStart).trim();
+                    } else if (newStart !== '') {
+                        posDif = newStart.length + 1;
+                        lineContent = newStart + ' ' + lineContent;
+                    }
+                    cm.replaceRange(lineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen});
+                    cm.setCursor({line: cursor.line, ch: cursor.ch + posDif});
+                }
+
+                function wrapLine(start, end) {
+                    let cursor = cm.getCursor();
+                    let lineContent = cm.getLine(cursor.line);
+                    let lineLen = lineContent.length;
+                    let newLineContent = lineContent;
+
+                    if (lineContent.indexOf(start) === 0 && lineContent.slice(-end.length) === end) {
+                        newLineContent = lineContent.slice(start.length, lineContent.length - end.length);
+                    } else {
+                        newLineContent = `${start}${lineContent}${end}`;
+                    }
+
+                    cm.replaceRange(newLineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen});
+                    cm.setCursor({line: cursor.line, ch: cursor.ch + (newLineContent.length - lineLen)});
+                }
+
+                function wrapSelection(start, end) {
+                    let selection = cm.getSelection();
+                    if (selection === '') return wrapLine(start, end);
+                    let newSelection = selection;
+                    let frontDiff = 0;
+                    let endDiff = 0;
+
+                    if (selection.indexOf(start) === 0 && selection.slice(-end.length) === end) {
+                        newSelection = selection.slice(start.length, selection.length - end.length);
+                        endDiff = -(end.length + start.length);
+                    } else {
+                        newSelection = `${start}${selection}${end}`;
+                        endDiff = start.length + end.length;
+                    }
+
+                    let selections = cm.listSelections()[0];
+                    cm.replaceSelection(newSelection);
+                    let headFirst = selections.head.ch <= selections.anchor.ch;
+                    selections.head.ch += headFirst ? frontDiff : endDiff;
+                    selections.anchor.ch += headFirst ? endDiff : frontDiff;
+                    cm.setSelections([selections]);
+                }
+
+                // Handle image upload and add image into markdown content
+                function uploadImage(file) {
+                    if (file === null || file.type.indexOf('image') !== 0) return;
+                    let ext = 'png';
+
+                    if (file.name) {
+                        let fileNameMatches = file.name.match(/\.(.+)$/);
+                        if (fileNameMatches.length > 1) ext = fileNameMatches[1];
+                    }
+
+                    // Insert image into markdown
+                    let id = "image-" + Math.random().toString(16).slice(2);
+                    let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
+                    let selectedText = cm.getSelection();
+                    let placeHolderText = `![${selectedText}](${placeholderImage})`;
+                    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.showExternal(image => {
+                        let selectedText = cm.getSelection();
+                        let newText = "![" + (selectedText || image.name) + "](" + image.thumbs.display + ")";
+                        cm.focus();
+                        cm.replaceSelection(newText);
+                        cm.setCursor(cursorPos.line, cursorPos.ch + newText.length);
+                    });
+                }
+
+                // Update the data models and rendered output
                 function update(instance) {
                     let content = instance.getValue();
                     element.val(content);
@@ -267,6 +442,9 @@ module.exports = function (ngApp, events) {
                 }
                 update(cm);
 
+                // Listen to commands from parent scope
+                scope.$on('md-insert-link', showLinkSelector);
+                scope.$on('md-insert-image', showImageManager);
                 scope.$on('markdown-update', (event, value) => {
                     cm.setValue(value);
                     element.val(value);
@@ -287,8 +465,7 @@ module.exports = function (ngApp, events) {
             restrict: 'A',
             link: function (scope, element, attrs) {
 
-                // Elements
-                const $input = element.find('[markdown-input] textarea').first();
+                // Editor Elements
                 const $display = element.find('.markdown-display').first();
                 const $insertImage = element.find('button[data-action="insertImage"]');
                 const $insertEntityLink = element.find('button[data-action="insertEntityLink"]');
@@ -299,11 +476,9 @@ module.exports = function (ngApp, events) {
                     window.open(this.getAttribute('href'));
                 });
 
-                let currentCaretPos = 0;
-
-                $input.blur(event => {
-                    currentCaretPos = $input[0].selectionStart;
-                });
+                // Editor UI Actions
+                $insertEntityLink.click(e => {scope.$broadcast('md-insert-link');});
+                $insertImage.click(e => {scope.$broadcast('md-insert-image');});
 
                 // Handle scroll sync event from editor scroll
                 $rootScope.$on('markdown-scroll', (event, lineCount) => {
@@ -315,140 +490,6 @@ module.exports = function (ngApp, events) {
                         }, {queue: false, duration: 200, easing: 'linear'});
                     }
                 });
-
-                // Editor key-presses
-                $input.keydown(event => {
-                    // Insert image shortcut
-                    if (event.which === 73 && event.ctrlKey && event.shiftKey) {
-                        event.preventDefault();
-                        let caretPos = $input[0].selectionStart;
-                        let currentContent = $input.val();
-                        const mdImageText = "![](http://)";
-                        $input.val(currentContent.substring(0, caretPos) + mdImageText + currentContent.substring(caretPos));
-                        $input.focus();
-                        $input[0].selectionStart = caretPos + ("![](".length);
-                        $input[0].selectionEnd = caretPos + ('![](http://'.length);
-                        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 = "![" + image.name + "](" + image.thumbs.display + ")";
-                        $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);
-                }
-
             }
         }
     }]);
index dc6802e12ba6646be4099f759773f2368e2c5ffe..2ef062a5b2856187c01250ce619eade3e0a3ec94 100644 (file)
@@ -17,6 +17,7 @@ let axiosInstance = axios.create({
         'baseURL': window.baseUrl('')
     }
 });
+window.$http = axiosInstance;
 
 Vue.prototype.$http = axiosInstance;
 
index 04951b174e02998f9ba2681d2697d18510a90892..4f4c1fbe0fe6744afa82eda93739f89fbafd5d75 100644 (file)
@@ -1,5 +1,7 @@
 "use strict";
 
+const Code = require('../code');
+
 /**
  * Handle pasting images from clipboard.
  * @param e  - event
@@ -50,23 +52,183 @@ function editorPaste(e, editor) {
 function registerEditorShortcuts(editor) {
     // Headers
     for (let i = 1; i < 5; i++) {
-        editor.addShortcut('meta+' + i, '', ['FormatBlock', false, 'h' + i]);
+        editor.shortcuts.add('meta+' + i, '', ['FormatBlock', false, 'h' + (i+1)]);
     }
 
     // Other block shortcuts
-    editor.addShortcut('meta+q', '', ['FormatBlock', false, 'blockquote']);
-    editor.addShortcut('meta+d', '', ['FormatBlock', false, 'p']);
-    editor.addShortcut('meta+e', '', ['FormatBlock', false, 'pre']);
-    editor.addShortcut('meta+shift+E', '', ['FormatBlock', false, 'code']);
+    editor.shortcuts.add('meta+5', '', ['FormatBlock', false, 'p']);
+    editor.shortcuts.add('meta+d', '', ['FormatBlock', false, 'p']);
+    editor.shortcuts.add('meta+6', '', ['FormatBlock', false, 'blockquote']);
+    editor.shortcuts.add('meta+q', '', ['FormatBlock', false, 'blockquote']);
+    editor.shortcuts.add('meta+7', '', ['codeeditor', false, 'pre']);
+    editor.shortcuts.add('meta+e', '', ['codeeditor', false, 'pre']);
+    editor.shortcuts.add('meta+8', '', ['FormatBlock', false, 'code']);
+    editor.shortcuts.add('meta+shift+E', '', ['FormatBlock', false, 'code']);
+    // Loop through callout styles
+    editor.shortcuts.add('meta+9', '', function() {
+        let selectedNode = editor.selection.getNode();
+        let formats = ['info', 'success', 'warning', 'danger'];
+
+        if (!selectedNode || selectedNode.className.indexOf('callout') === -1) {
+            editor.formatter.apply('calloutinfo');
+            return;
+        }
+
+        for (let i = 0; i < formats.length; i++) {
+            if (selectedNode.className.indexOf(formats[i]) === -1) continue;
+            let newFormat = (i === formats.length -1) ? formats[0] : formats[i+1];
+            editor.formatter.apply('callout' + newFormat);
+            return;
+        }
+        editor.formatter.apply('p');
+    });
+}
+
+
+/**
+ * Create and enable our custom code plugin
+ */
+function codePlugin() {
+
+    function elemIsCodeBlock(elem) {
+        return elem.className === 'CodeMirrorContainer';
+    }
+
+    function showPopup(editor) {
+        let selectedNode = editor.selection.getNode();
+
+        if (!elemIsCodeBlock(selectedNode)) {
+            let providedCode = editor.selection.getNode().textContent;
+            window.vues['code-editor'].open(providedCode, '', (code, lang) => {
+                let wrap = document.createElement('div');
+                wrap.innerHTML = `<pre><code class="language-${lang}"></code></pre>`;
+                wrap.querySelector('code').innerText = code;
+
+                editor.formatter.toggle('pre');
+                let node = editor.selection.getNode();
+                editor.dom.setHTML(node, wrap.querySelector('pre').innerHTML);
+                editor.fire('SetContent');
+            });
+            return;
+        }
+
+        let lang = selectedNode.hasAttribute('data-lang') ? selectedNode.getAttribute('data-lang') : '';
+        let currentCode = selectedNode.querySelector('textarea').textContent;
+
+        window.vues['code-editor'].open(currentCode, lang, (code, lang) => {
+            let editorElem = selectedNode.querySelector('.CodeMirror');
+            let cmInstance = editorElem.CodeMirror;
+            if (cmInstance) {
+                Code.setContent(cmInstance, code);
+                Code.setMode(cmInstance, lang);
+            }
+            let textArea = selectedNode.querySelector('textarea');
+            if (textArea) textArea.textContent = code;
+            selectedNode.setAttribute('data-lang', lang);
+        });
+    }
+
+    function codeMirrorContainerToPre($codeMirrorContainer) {
+        let textArea = $codeMirrorContainer[0].querySelector('textarea');
+        let code = textArea.textContent;
+        let lang = $codeMirrorContainer[0].getAttribute('data-lang');
+
+        $codeMirrorContainer.removeAttr('contentEditable');
+        let $pre = $('<pre></pre>');
+        $pre.append($('<code></code>').each((index, elem) => {
+            // Needs to be textContent since innerText produces BR:s
+            elem.textContent = code;
+        }).attr('class', `language-${lang}`));
+        $codeMirrorContainer.replaceWith($pre);
+    }
+
+    window.tinymce.PluginManager.add('codeeditor', function(editor, url) {
+
+        let $ = editor.$;
+
+        editor.addButton('codeeditor', {
+            text: 'Code block',
+            icon: false,
+            cmd: 'codeeditor'
+        });
+
+        editor.addCommand('codeeditor', () => {
+            showPopup(editor);
+        });
+
+        // Convert
+        editor.on('PreProcess', function (e) {
+            $('div.CodeMirrorContainer', e.node).
+            each((index, elem) => {
+                let $elem = $(elem);
+                codeMirrorContainerToPre($elem);
+            });
+        });
+
+        editor.on('dblclick', event => {
+            let selectedNode = editor.selection.getNode();
+            if (!elemIsCodeBlock(selectedNode)) return;
+            showPopup(editor);
+        });
+
+        editor.on('SetContent', function () {
+
+            // Recover broken codemirror instances
+            $('.CodeMirrorContainer').filter((index ,elem) => {
+                return typeof elem.querySelector('.CodeMirror').CodeMirror === 'undefined';
+            }).each((index, elem) => {
+                codeMirrorContainerToPre($(elem));
+            });
+
+            let codeSamples = $('body > pre').filter((index, elem) => {
+                return elem.contentEditable !== "false";
+            });
+
+            if (!codeSamples.length) return;
+            editor.undoManager.transact(function () {
+                codeSamples.each((index, elem) => {
+                    Code.wysiwygView(elem);
+                });
+            });
+        });
+
+    });
+}
+
+function hrPlugin() {
+    window.tinymce.PluginManager.add('customhr', function (editor) {
+        editor.addCommand('InsertHorizontalRule', function () {
+            let hrElem = document.createElement('hr');
+            let cNode = editor.selection.getNode();
+            let parentNode = cNode.parentNode;
+            parentNode.insertBefore(hrElem, cNode);
+        });
+
+        editor.addButton('hr', {
+            icon: 'hr',
+            tooltip: 'Horizontal line',
+            cmd: 'InsertHorizontalRule'
+        });
+
+        editor.addMenuItem('hr', {
+            icon: 'hr',
+            text: 'Horizontal line',
+            cmd: 'InsertHorizontalRule',
+            context: 'insert'
+        });
+    });
 }
 
 module.exports = function() {
+    hrPlugin();
+    codePlugin();
     let settings = {
         selector: '#html-editor',
         content_css: [
             window.baseUrl('/css/styles.css'),
             window.baseUrl('/libs/material-design-iconic-font/css/material-design-iconic-font.min.css')
         ],
+        branding: false,
         body_class: 'page-content',
         browser_spellcheck: true,
         relative_urls: false,
@@ -77,10 +239,10 @@ module.exports = function() {
         paste_data_images: false,
         extended_valid_elements: 'pre[*]',
         automatic_uploads: false,
-        valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
-        plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codesample",
+        valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre]",
+        plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor",
         imagetools_toolbar: 'imageoptions',
-        toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen codesample",
+        toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen",
         content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}",
         style_formats: [
             {title: "Header Large", format: "h2"},
@@ -89,20 +251,25 @@ module.exports = function() {
             {title: "Header Tiny", format: "h5"},
             {title: "Paragraph", format: "p", exact: true, classes: ''},
             {title: "Blockquote", format: "blockquote"},
-            {title: "Code Block", icon: "code", format: "pre"},
+            {title: "Code Block", icon: "code", cmd: 'codeeditor', format: 'codeeditor'},
             {title: "Inline Code", icon: "code", inline: "code"},
             {title: "Callouts", items: [
-                {title: "Success", block: 'p', exact: true, attributes : {'class' : 'callout success'}},
-                {title: "Info", block: 'p', exact: true, attributes : {'class' : 'callout info'}},
-                {title: "Warning", block: 'p', exact: true, attributes : {'class' : 'callout warning'}},
-                {title: "Danger", block: 'p', exact: true, attributes : {'class' : 'callout danger'}}
-            ]}
+                {title: "Info", format: 'calloutinfo'},
+                {title: "Success", format: 'calloutsuccess'},
+                {title: "Warning", format: 'calloutwarning'},
+                {title: "Danger", format: 'calloutdanger'}
+            ]},
         ],
         style_formats_merge: false,
         formats: {
+            codeeditor: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div'},
             alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-left'},
             aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-center'},
             alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
+            calloutsuccess: {block: 'p', exact: true, attributes: {class: 'callout success'}},
+            calloutinfo: {block: 'p', exact: true, attributes: {class: 'callout info'}},
+            calloutwarning: {block: 'p', exact: true, attributes: {class: 'callout warning'}},
+            calloutdanger: {block: 'p', exact: true, attributes: {class: 'callout danger'}}
         },
         file_browser_callback: function (field_name, url, type, win) {
 
diff --git a/resources/assets/js/vues/code-editor.js b/resources/assets/js/vues/code-editor.js
new file mode 100644 (file)
index 0000000..35a98cc
--- /dev/null
@@ -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
+};
\ No newline at end of file
index 515ca3bc9dfb8d8c327d1dc5cb8521a3df279442..8cb790d2456565a68bd50258506dec48aa186195 100644 (file)
@@ -149,7 +149,7 @@ let methods = {
 
     updateSearch(e) {
         e.preventDefault();
-        window.location = '/search?term=' + encodeURIComponent(this.termString);
+        window.location = window.baseUrl('/search?term=' + encodeURIComponent(this.termString));
     },
 
     enableDate(optionName) {
@@ -192,4 +192,4 @@ function created() {
 
 module.exports = {
     data, computed, methods, created
-};
\ No newline at end of file
+};
index 8cc1dd6560ccfc175e846cf9316a8c6ac7aceb0e..31d833bfb864a8308e87a062f0a353e2073f35fc 100644 (file)
@@ -7,12 +7,15 @@ function exists(id) {
 let vueMapping = {
     'search-system': require('./search'),
     'entity-dashboard': require('./entity-search'),
+    'code-editor': require('./code-editor')
 };
 
+window.vues = {};
+
 Object.keys(vueMapping).forEach(id => {
     if (exists(id)) {
         let config = vueMapping[id];
         config.el = '#' + id;
-        new Vue(config);
+        window.vues[id] = new Vue(config);
     }
 });
\ No newline at end of file
index 9f9e38f55c35a1709d172eef767e757bb818ee6e..bd85218a5dd581285101d9fc593b7f14d8eede7f 100644 (file)
@@ -248,6 +248,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
   -webkit-tap-highlight-color: transparent;
   -webkit-font-variant-ligatures: contextual;
   font-variant-ligatures: contextual;
+  &:after {
+    content: none;
+    display: none;
+  }
 }
 .CodeMirror-wrap pre {
   word-wrap: break-word;
index 5328057d9da7af1d81767d2fd1a05d8d6b486541..12babae7313efc3d9503cfb4e31af89ee8c1834a 100644 (file)
@@ -466,4 +466,17 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 
 .image-picker .none {
   display: none;
+}
+
+#code-editor .CodeMirror {
+  height: 400px;
+}
+
+#code-editor .lang-options {
+  max-width: 400px;
+  margin-bottom: $-s;
+  a {
+    margin-right: $-xs;
+    text-decoration: underline;
+  }
 }
\ No newline at end of file
index 1fc8128966aa985f360003332b8a500628d5c89a..392e9ec3e1d1d3450708cd8a2b4cd0ec6e28b39f 100644 (file)
@@ -32,7 +32,7 @@
 #markdown-editor {
   position: relative;
   z-index: 5;
-  textarea {
+  #markdown-editor-input {
     font-family: 'Roboto Mono', monospace;
     font-style: normal;
     font-weight: 400;
index 12bd17076a7e85083ae28546b05f54b4f324afe1..ae8dd3ff51937b67f6bb62e15fb4b3ca7e260502 100644 (file)
@@ -142,7 +142,6 @@ form.search-box {
   color: #aaa;
   padding: 0 $-xs;
 }
-
 .faded {
   a, button, span, span > div {
     color: #666;
@@ -155,7 +154,6 @@ form.search-box {
       text-decoration: none;
     }
   }
-
 }
 
 .faded span.faded-text {
@@ -175,6 +173,15 @@ form.search-box {
   &:last-child {
     padding-right: 0;
   }
+  &:first-child {
+    padding-left: 0;
+  }
+}
+
+
+.action-buttons .dropdown-container:last-child a {
+  padding-right: 0;
+  padding-left: $-s;
 }
 .action-buttons {
   text-align: right;
@@ -190,6 +197,25 @@ form.search-box {
   }
 }
 
+@include smaller-than($m) {
+  .breadcrumbs .text-button, .action-buttons .text-button {
+    padding: $-s $-xs;
+  }
+  .action-buttons .dropdown-container:last-child a {
+    padding-left: $-xs;
+  }
+  .breadcrumbs .text-button {
+    font-size: 0;
+  }
+  .breadcrumbs a i {
+    font-size: $fs-m;
+    padding-right: 0;
+  }
+  .breadcrumbs span.sep {
+    padding: 0 $-xxs;
+  }
+}
+
 .nav-tabs {
   text-align: center;
   a, .tab-item {
index 4eaa492e735e74cd47b99a2f0173236f0d90f577..2ef4bd16d80abe4e8034ae7d5e3996df22da3b3d 100644 (file)
@@ -135,8 +135,31 @@ pre {
   font-size: 12px;
   background-color: #f5f5f5;
   border: 1px solid #DDD;
+  padding-left: 31px;
+  position: relative;
+  padding-top: 3px;
+  padding-bottom: 3px;
+  &:after {
+    content: '';
+    display: block;
+    position: absolute;
+    top: 0;
+    width: 29px;
+    left: 0;
+    background-color: #f5f5f5;
+    height: 100%;
+    border-right: 1px solid #DDD;
+  }
 }
 
+@media print {
+  pre {
+    padding-left: 12px;
+  }
+  pre:after {
+    display: none;
+  }
+}
 
 blockquote {
   display: block;
@@ -182,6 +205,7 @@ pre code {
   border: 0;
   font-size: 1em;
   display: block;
+  line-height: 1.6;
 }
 /*
  * Text colors
index b9108702a288902c7e49ad46a9e42a023baf5389..334502d05b84dc0e09ec37b863d02d49d1d88e56 100644 (file)
@@ -20,5 +20,13 @@ return [
     'image_preview' => 'Image Preview',
     'image_upload_success' => 'Image uploaded successfully',
     'image_update_success' => 'Image details successfully updated',
-    'image_delete_success' => 'Image successfully deleted'
+    'image_delete_success' => 'Image successfully deleted',
+
+    /**
+     * Code editor
+     */
+    'code_editor' => 'Edit Code',
+    'code_language' => 'Code Language',
+    'code_content' => 'Code Content',
+    'code_save' => 'Save Code',
 ];
\ No newline at end of file
index 31163e87e75bc7393f3425b0e417e8ab1e4e1e31..3eec7737f913c493b8cf60b45e08b4a4cb64fd8c 100644 (file)
@@ -121,6 +121,8 @@ return [
         'nl' => 'Nederlands',
         'pt_BR' => 'Português do Brasil',
         'sk' => 'Slovensky',
+        'ja' => '日本語',
+        'pl' => 'Polski',
     ]
     ///////////////////////////////////
 ];
index 41d051c5fff657df40462a1eba0dfbc6596d8e9e..015bfdff03a858eec70e09ec4b0fadd5d6c0a100 100644 (file)
@@ -10,7 +10,7 @@ return [
     | these language lines according to your application's requirements.
     |
     */
-    'failed' => 'Ces informations ne correspondent a aucun compte.',
+    'failed' => 'Ces informations ne correspondent à aucun compte.',
     'throttle' => "Trop d'essais, veuillez réessayer dans :seconds secondes.",
 
     /**
@@ -26,7 +26,7 @@ return [
     'password' => 'Mot de passe',
     'password_confirm' => 'Confirmez le mot de passe',
     'password_hint' => 'Doit faire plus de 5 caractères',
-    'forgot_password' => 'Mot de passe oublié?',
+    'forgot_password' => 'Mot de passe oublié ?',
     'remember_me' => 'Se souvenir de moi',
     'ldap_email_hint' => "Merci d'entrer une adresse e-mail pour ce compte",
     'create_account' => 'Créer un compte',
@@ -35,9 +35,9 @@ return [
     'social_registration_text' => "S'inscrire et se connecter avec un réseau social",
 
     'register_thanks' => 'Merci pour votre enregistrement',
-    'register_confirm' => 'Vérifiez vos e-mails et cliquer sur le lien de confirmation pour rejoindre :appName.',
+    'register_confirm' => 'Vérifiez vos e-mails et cliquez sur le lien de confirmation pour rejoindre :appName.',
     'registrations_disabled' => "L'inscription est désactivée pour le moment",
-    'registration_email_domain_invalid' => 'Cette adresse e-mail ne peux pas adcéder à l\'application',
+    'registration_email_domain_invalid' => 'Cette adresse e-mail ne peut pas accéder à l\'application',
     'register_success' => 'Merci pour votre inscription. Vous êtes maintenant inscrit(e) et connecté(e)',
 
 
@@ -51,7 +51,7 @@ return [
     'reset_password_success' => 'Votre mot de passe a été réinitialisé avec succès.',
 
     'email_reset_subject' => 'Réinitialisez votre mot de passe pour :appName',
-    'email_reset_text' => 'Vous recevez cet e-mail parceque nous avons reçu une demande de réinitialisation pour votre compte',
+    'email_reset_text' => 'Vous recevez cet e-mail parce que nous avons reçu une demande de réinitialisation pour votre compte',
     'email_reset_not_requested' => 'Si vous n\'avez pas effectué cette demande, vous pouvez ignorer cet e-mail.',
 
 
@@ -59,11 +59,11 @@ return [
      * Email Confirmation
      */
     'email_confirm_subject' => 'Confirmez votre adresse e-mail pour :appName',
-    'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName!',
-    'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous:',
+    'email_confirm_greeting' => 'Merci d\'avoir rejoint :appName !',
+    'email_confirm_text' => 'Merci de confirmer en cliquant sur le lien ci-dessous :',
     'email_confirm_action' => 'Confirmez votre adresse e-mail',
     'email_confirm_send_error' => 'La confirmation par e-mail est requise mais le système n\'a pas pu envoyer l\'e-mail. Contactez l\'administrateur système.',
-    'email_confirm_success' => 'Votre adresse e-mail a été confirmée!',
+    'email_confirm_success' => 'Votre adresse e-mail a été confirmée !',
     'email_confirm_resent' => 'L\'e-mail de confirmation a été ré-envoyé. Vérifiez votre boîte de récéption.',
 
     'email_not_confirmed' => 'Adresse e-mail non confirmée',
index 5eb4b8fa8509a1f844b4141980f099fd421d4139..7a8ec55b605d90f53bc27559b6988b535b6231dd 100644 (file)
@@ -9,7 +9,7 @@ return [
     'back' => 'Retour',
     'save' => 'Enregistrer',
     'continue' => 'Continuer',
-    'select' => 'Selectionner',
+    'select' => 'Sélectionner',
 
     /**
      * Form Labels
@@ -53,6 +53,6 @@ return [
     /**
      * Email Content
      */
-    'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur:',
+    'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer sur le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur :',
     'email_rights' => 'Tous droits réservés',
 ];
index c618bab0808e0a8934251f3d8ea0700ab719a719..0d89993e9d8043ba4c6e3f9b9f6f5c24079ad5f6 100644 (file)
@@ -12,7 +12,7 @@ return [
     'recently_update' => 'Mis à jour récemment',
     'recently_viewed' => 'Vus récemment',
     'recent_activity' => 'Activité récente',
-    'create_now' => 'En créer un récemment',
+    'create_now' => 'En créer un maintenant',
     'revisions' => 'Révisions',
     'meta_created' => 'Créé :timeLength',
     'meta_created_name' => 'Créé :timeLength par :user',
@@ -59,8 +59,8 @@ return [
     'books_create' => 'Créer un nouveau livre',
     'books_delete' => 'Supprimer un livre',
     'books_delete_named' => 'Supprimer le livre :bookName',
-    'books_delete_explain' => 'Ceci va supprimer le livre nommé \':bookName\', Tous les chapitres et pages seront supprimés.',
-    'books_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer ce livre?',
+    'books_delete_explain' => 'Ceci va supprimer le livre nommé \':bookName\', tous les chapitres et pages seront supprimés.',
+    'books_delete_confirmation' => 'Êtes-vous sûr(e) de vouloir supprimer ce livre ?',
     'books_edit' => 'Modifier le livre',
     'books_edit_named' => 'Modifier le livre :bookName',
     'books_form_book_name' => 'Nom du livre',
@@ -90,18 +90,18 @@ return [
     'chapters_create' => 'Créer un nouveau chapitre',
     'chapters_delete' => 'Supprimer le chapitre',
     'chapters_delete_named' => 'Supprimer le chapitre :chapterName',
-    'chapters_delete_explain' => 'Ceci va supprimer le chapitre \':chapterName\', Toutes les pages seront déplacée dans le livre parent.',
-    'chapters_delete_confirm' => 'Etes-vous sûr(e) de vouloir supprimer ce chapitre?',
+    'chapters_delete_explain' => 'Ceci va supprimer le chapitre \':chapterName\', toutes les pages seront déplacées dans le livre parent.',
+    'chapters_delete_confirm' => 'Etes-vous sûr(e) de vouloir supprimer ce chapitre ?',
     'chapters_edit' => 'Modifier le chapitre',
     'chapters_edit_named' => 'Modifier le chapitre :chapterName',
     'chapters_save' => 'Enregistrer le chapitre',
-    'chapters_move' => 'Déplace le chapitre',
+    'chapters_move' => 'Déplacer le chapitre',
     'chapters_move_named' => 'Déplacer le chapitre :chapterName',
     'chapter_move_success' => 'Chapitre déplacé dans :bookName',
     'chapters_permissions' => 'Permissions du chapitre',
-    'chapters_empty' => 'Il n\'y a pas de pages dans ce chapitre actuellement.',
+    'chapters_empty' => 'Il n\'y a pas de page dans ce chapitre actuellement.',
     'chapters_permissions_active' => 'Permissions du chapitre activées',
-    'chapters_permissions_success' => 'Permissions du chapitres mises à jour',
+    'chapters_permissions_success' => 'Permissions du chapitre mises à jour',
 
     /**
      * Pages
@@ -118,8 +118,8 @@ return [
     'pages_delete_draft' => 'Supprimer le brouillon',
     'pages_delete_success' => 'Page supprimée',
     'pages_delete_draft_success' => 'Brouillon supprimé',
-    'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page?',
-    'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon?',
+    'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page ?',
+    'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon ?',
     'pages_editing_named' => 'Modification de la page :pageName',
     'pages_edit_toggle_header' => 'Afficher/cacher l\'en-tête',
     'pages_edit_save_draft' => 'Enregistrer le brouillon',
@@ -131,7 +131,7 @@ return [
     'pages_edit_discard_draft' => 'Ecarter le brouillon',
     'pages_edit_set_changelog' => 'Remplir le journal des changements',
     'pages_edit_enter_changelog_desc' => 'Entrez une brève description des changements effectués',
-    'pages_edit_enter_changelog' => 'Entrez dans le journal des changements',
+    'pages_edit_enter_changelog' => 'Entrer dans le journal des changements',
     'pages_save' => 'Enregistrez la page',
     'pages_title' => 'Titre de la page',
     'pages_name' => 'Nom de la page',
@@ -139,7 +139,7 @@ return [
     'pages_md_preview' => 'Prévisualisation',
     'pages_md_insert_image' => 'Insérer une image',
     'pages_md_insert_link' => 'Insérer un lien',
-    'pages_not_in_chapter' => 'La page n\'est pas dans un chanpitre',
+    'pages_not_in_chapter' => 'La page n\'est pas dans un chapitre',
     'pages_move' => 'Déplacer la page',
     'pages_move_success' => 'Page déplacée à ":parentName"',
     'pages_permissions' => 'Permissions de la page',
@@ -160,15 +160,15 @@ return [
     'pages_initial_revision' => 'Publication initiale',
     'pages_initial_name' => 'Nouvelle page',
     'pages_editing_draft_notification' => 'Vous éditez actuellement un brouillon qui a été sauvé :timeDiff.',
-    'pages_draft_edited_notification' => 'La page a été mise à jour depuis votre dernière visit. Vous devriez écarter ce brouillon.',
+    'pages_draft_edited_notification' => 'La page a été mise à jour depuis votre dernière visite. Vous devriez écarter ce brouillon.',
     'pages_draft_edit_active' => [
-        'start_a' => ':count utilisateurs ont commencé a éditer cette page',
+        'start_a' => ':count utilisateurs ont commencé à éditer cette page',
         'start_b' => ':userName a commencé à éditer cette page',
         'time_a' => 'depuis la dernière sauvegarde',
         'time_b' => 'dans les :minCount dernières minutes',
-        'message' => ':start :time. Attention a ne pas écraser les mises à jour de quelqu\'un d\'autre!',
+        'message' => ':start :time. Attention à ne pas écraser les mises à jour de quelqu\'un d\'autre !',
     ],
-    'pages_draft_discarded' => 'Brouuillon écarté, la page est dans sa version actuelle.',
+    'pages_draft_discarded' => 'Brouillon écarté, la page est dans sa version actuelle.',
 
     /**
      * Editor sidebar
@@ -210,9 +210,9 @@ return [
      */
     'profile_user_for_x' => 'Utilisateur depuis :time',
     'profile_created_content' => 'Contenu créé',
-    'profile_not_created_pages' => ':userName n\'a pas créé de pages',
-    'profile_not_created_chapters' => ':userName n\'a pas créé de chapitres',
-    'profile_not_created_books' => ':userName n\'a pas créé de livres',
+    'profile_not_created_pages' => ':userName n\'a pas créé de page',
+    'profile_not_created_chapters' => ':userName n\'a pas créé de chapitre',
+    'profile_not_created_books' => ':userName n\'a pas créé de livre',
 
     /**
      * Comments
index 402eeb405c8d8f42aeeeedaadc30fa566343dadf..9e20147b60a474e48b844267a37c9c3283375f56 100644 (file)
@@ -18,21 +18,21 @@ return [
     'ldap_fail_anonymous' => 'L\'accès LDAP anonyme n\'a pas abouti',
     'ldap_fail_authed' => 'L\'accès LDAP n\'a pas abouti avec cet utilisateur et ce mot de passe',
     'ldap_extension_not_installed' => 'L\'extention LDAP PHP n\'est pas installée',
-    'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed',
-    'social_no_action_defined' => 'No action defined',
-    'social_account_in_use' => 'Cet compte :socialAccount est déjà utilisé. Essayez de vous connecter via :socialAccount.',
-    'social_account_email_in_use' => 'L\'email :email Est déjà utilisé. Si vous avez déjà un compte :socialAccount, vous pouvez le joindre à votre profil existant.',
+    'ldap_cannot_connect' => 'Impossible de se connecter au serveur LDAP, la connexion initiale a échoué',
+    'social_no_action_defined' => 'Pas d\'action définie',
+    'social_account_in_use' => 'Ce compte :socialAccount est déjà utilisé. Essayez de vous connecter via :socialAccount.',
+    'social_account_email_in_use' => 'L\'email :email est déjà utilisé. Si vous avez déjà un compte :socialAccount, vous pouvez le joindre à votre profil existant.',
     'social_account_existing' => 'Ce compte :socialAccount est déjà rattaché à votre profil.',
     'social_account_already_used_existing' => 'Ce compte :socialAccount est déjà utilisé par un autre utilisateur.',
     'social_account_not_used' => 'Ce compte :socialAccount n\'est lié à aucun utilisateur. ',
     'social_account_register_instructions' => 'Si vous n\'avez pas encore de compte, vous pouvez le lier avec l\'option :socialAccount.',
-    'social_driver_not_found' => 'Social driver not found',
-    'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.',
+    'social_driver_not_found' => 'Pilote de compte social absent',
+    'social_driver_not_configured' => 'Vos préférences pour le compte :socialAccount sont incorrectes.',
 
     // System
-    'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.',
+    'path_not_writable' => 'Impossible d\'écrire dans :filePath. Assurez-vous d\'avoir les droits d\'écriture sur le serveur',
     'cannot_get_image_from_url' => 'Impossible de récupérer l\'image depuis :url',
-    'cannot_create_thumbs' => 'Le serveur ne peux pas créer de miniatures, vérifier que l\extensions GD PHP est installée.',
+    'cannot_create_thumbs' => 'Le serveur ne peut pas créer de miniature, vérifier que l\'extension PHP GD est installée.',
     'server_upload_limit' => 'La taille du fichier est trop grande.',
     'image_upload_error' => 'Une erreur est survenue pendant l\'envoi de l\'image',
 
@@ -57,7 +57,7 @@ return [
 
     // Roles
     'role_cannot_be_edited' => 'Ce rôle ne peut pas être modifié',
-    'role_system_cannot_be_deleted' => 'Ceci est un rôle du système et on ne peut pas le supprimer',
+    'role_system_cannot_be_deleted' => 'Ceci est un rôle du système et ne peut pas être supprimé',
     'role_registration_default_cannot_delete' => 'Ce rôle ne peut pas être supprimé tant qu\'il est le rôle par défaut',
 
     // Error pages
index 7be81da2327ce16022b7f0d72591235a8e0a35d7..484b4b20c295c6f7862e72cfff6cf3a22e2ee172 100644 (file)
@@ -16,7 +16,7 @@ return [
     'password' => 'Les mots de passe doivent faire au moins 6 caractères et correspondre à la confirmation.',
     'user' => "Nous n'avons pas trouvé d'utilisateur avec cette adresse.",
     'token' => 'Le jeton de réinitialisation est invalide.',
-    'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe!',
-    'reset' => 'Votre mot de passe a été réinitialisé!',
+    'sent' => 'Nous vous avons envoyé un lien de réinitialisation de mot de passe !',
+    'reset' => 'Votre mot de passe a été réinitialisé !',
 
 ];
index 8a375652781f55fbc6c89e50c5225da1872afcf8..92e623795dcbc851cbf006e993859d135b2b624f 100644 (file)
@@ -19,27 +19,27 @@ return [
     'app_settings' => 'Préférences de l\'application',
     'app_name' => 'Nom de l\'application',
     'app_name_desc' => 'Ce nom est affiché dans l\'en-tête et les e-mails.',
-    'app_name_header' => 'Afficher le nom dans l\'en-tête?',
-    'app_public_viewing' => 'Accepter le visionnage public des pages?',
-    'app_secure_images' => 'Activer l\'ajout d\'image sécurisé?',
+    'app_name_header' => 'Afficher le nom dans l\'en-tête ?',
+    'app_public_viewing' => 'Accepter le visionnage public des pages ?',
+    'app_secure_images' => 'Activer l\'ajout d\'image sécurisé ?',
     'app_secure_images_desc' => 'Pour des questions de performances, toutes les images sont publiques. Cette option ajoute une chaîne aléatoire difficile à deviner dans les URLs des images.',
     'app_editor' => 'Editeur des pages',
     'app_editor_desc' => 'Sélectionnez l\'éditeur qui sera utilisé pour modifier les pages.',
     'app_custom_html' => 'HTML personnalisé dans l\'en-tête',
-    'app_custom_html_desc' => 'Le contenu inséré ici sera jouté en bas de la balise <head> de toutes les pages. Vous pouvez l\'utiliser pour ajouter du CSS personnalisé ou un tracker analytique.',
+    'app_custom_html_desc' => 'Le contenu inséré ici sera ajouté en bas de la balise <head> de toutes les pages. Vous pouvez l\'utiliser pour ajouter du CSS personnalisé ou un tracker analytique.',
     'app_logo' => 'Logo de l\'Application',
     'app_logo_desc' => 'Cette image doit faire 43px de hauteur. <br>Les images plus larges seront réduites.',
     'app_primary_color' => 'Couleur principale de l\'application',
-    'app_primary_color_desc' => 'This should be a hex value. <br>Leave empty to reset to the default color.',
+    'app_primary_color_desc' => 'Cela devrait être une valeur hexadécimale. <br>Laisser vide pour rétablir la couleur par défaut.',
 
     /**
      * Registration settings
      */
 
     'reg_settings' => 'Préférence pour l\'inscription',
-    'reg_allow' => 'Accepter l\'inscription?',
+    'reg_allow' => 'Accepter l\'inscription ?',
     'reg_default_role' => 'Rôle par défaut lors de l\'inscription',
-    'reg_confirm_email' => 'Obliger la confirmation par e-mail?',
+    'reg_confirm_email' => 'Obliger la confirmation par e-mail ?',
     'reg_confirm_email_desc' => 'Si la restriction de domaine est activée, la confirmation sera automatiquement obligatoire et cette valeur sera ignorée.',
     'reg_confirm_restrict_domain' => 'Restreindre l\'inscription à un domaine',
     'reg_confirm_restrict_domain_desc' => 'Entrez une liste de domaines acceptés lors de l\'inscription, séparés par une virgule. Les utilisateur recevront un e-mail de confirmation à cette adresse. <br> Les utilisateurs pourront changer leur adresse après inscription s\'ils le souhaitent.',
@@ -57,17 +57,17 @@ return [
     'role_delete_confirm' => 'Ceci va supprimer le rôle \':roleName\'.',
     'role_delete_users_assigned' => 'Ce rôle a :userCount utilisateurs assignés. Vous pouvez choisir un rôle de remplacement pour ces utilisateurs.',
     'role_delete_no_migration' => "Ne pas assigner de nouveau rôle",
-    'role_delete_sure' => 'Êtes vous sûr(e) de vouloir supprimer ce rôle?',
+    'role_delete_sure' => 'Êtes vous sûr(e) de vouloir supprimer ce rôle ?',
     'role_delete_success' => 'Le rôle a été supprimé avec succès',
     'role_edit' => 'Modifier le rôle',
     'role_details' => 'Détails du rôle',
-    'role_name' => 'Nom du Rôle',
+    'role_name' => 'Nom du rôle',
     'role_desc' => 'Courte description du rôle',
     'role_system' => 'Permissions système',
     'role_manage_users' => 'Gérer les utilisateurs',
     'role_manage_roles' => 'Gérer les rôles et permissions',
     'role_manage_entity_permissions' => 'Gérer les permissions sur les livres, chapitres et pages',
-    'role_manage_own_entity_permissions' => 'Gérer les permissions de ses propres livres chapitres et pages',
+    'role_manage_own_entity_permissions' => 'Gérer les permissions de ses propres livres, chapitres, et pages',
     'role_manage_settings' => 'Gérer les préférences de l\'application',
     'role_asset' => 'Asset Permissions',
     'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.',
@@ -94,7 +94,7 @@ return [
     'users_delete' => 'Supprimer un utilisateur',
     'users_delete_named' => 'Supprimer l\'utilisateur :userName',
     'users_delete_warning' => 'Ceci va supprimer \':userName\' du système.',
-    'users_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cet utilisateur?',
+    'users_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cet utilisateur ?',
     'users_delete_success' => 'Utilisateurs supprimés avec succès',
     'users_edit' => 'Modifier l\'utilisateur',
     'users_edit_profile' => 'Modifier le profil',
@@ -106,7 +106,7 @@ return [
     'users_social_accounts_info' => 'Vous pouvez connecter des réseaux sociaux à votre compte pour vous connecter plus rapidement. Déconnecter un compte n\'enlèvera pas les accès autorisés précédemment sur votre compte de réseau social.',
     'users_social_connect' => 'Connecter le compte',
     'users_social_disconnect' => 'Déconnecter le compte',
-    'users_social_connected' => 'Votre compte :socialAccount a élté ajouté avec succès.',
+    'users_social_connected' => 'Votre compte :socialAccount a été ajouté avec succès.',
     'users_social_disconnected' => 'Votre compte :socialAccount a été déconnecté avec succès',
 
 ];
diff --git a/resources/lang/ja/activities.php b/resources/lang/ja/activities.php
new file mode 100644 (file)
index 0000000..b907e0c
--- /dev/null
@@ -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'      => '並び順を変更しました',
+
+];
diff --git a/resources/lang/ja/auth.php b/resources/lang/ja/auth.php
new file mode 100644 (file)
index 0000000..4d5aee8
--- /dev/null
@@ -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' => '確認メールを再送信',
+];
diff --git a/resources/lang/ja/common.php b/resources/lang/ja/common.php
new file mode 100644 (file)
index 0000000..3832948
--- /dev/null
@@ -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',
+];
diff --git a/resources/lang/ja/components.php b/resources/lang/ja/components.php
new file mode 100644 (file)
index 0000000..89ed33e
--- /dev/null
@@ -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' => '画像が削除されました'
+];
diff --git a/resources/lang/ja/entities.php b/resources/lang/ja/entities.php
new file mode 100644 (file)
index 0000000..8d21551
--- /dev/null
@@ -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はブックを作成していません',
+];
diff --git a/resources/lang/ja/errors.php b/resources/lang/ja/errors.php
new file mode 100644 (file)
index 0000000..5905237
--- /dev/null
@@ -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' => '回復までしばらくお待ちください。',
+];
diff --git a/resources/lang/ja/pagination.php b/resources/lang/ja/pagination.php
new file mode 100644 (file)
index 0000000..1ebcef7
--- /dev/null
@@ -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' => '&laquo; 前',
+    'next'     => '次 &raquo;',
+
+];
diff --git a/resources/lang/ja/passwords.php b/resources/lang/ja/passwords.php
new file mode 100644 (file)
index 0000000..17c82e2
--- /dev/null
@@ -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' => 'パスワードはリセットされました。',
+
+];
diff --git a/resources/lang/ja/settings.php b/resources/lang/ja/settings.php
new file mode 100644 (file)
index 0000000..b4cf57a
--- /dev/null
@@ -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」がプロフィールから接続解除されました。'
+    
+];
diff --git a/resources/lang/ja/validation.php b/resources/lang/ja/validation.php
new file mode 100644 (file)
index 0000000..e0fa3cb
--- /dev/null
@@ -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' => [],
+
+];
diff --git a/resources/lang/pl/activities.php b/resources/lang/pl/activities.php
new file mode 100644 (file)
index 0000000..5ef5aca
--- /dev/null
@@ -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',
+
+];
diff --git a/resources/lang/pl/auth.php b/resources/lang/pl/auth.php
new file mode 100644 (file)
index 0000000..740e067
--- /dev/null
@@ -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',
+];
\ No newline at end of file
diff --git a/resources/lang/pl/common.php b/resources/lang/pl/common.php
new file mode 100644 (file)
index 0000000..1c89636
--- /dev/null
@@ -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',
+];
\ No newline at end of file
diff --git a/resources/lang/pl/components.php b/resources/lang/pl/components.php
new file mode 100644 (file)
index 0000000..c1dbcd4
--- /dev/null
@@ -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',
+];
\ No newline at end of file
diff --git a/resources/lang/pl/entities.php b/resources/lang/pl/entities.php
new file mode 100644 (file)
index 0000000..30e853b
--- /dev/null
@@ -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',
+];
\ No newline at end of file
diff --git a/resources/lang/pl/errors.php b/resources/lang/pl/errors.php
new file mode 100644 (file)
index 0000000..633bf7a
--- /dev/null
@@ -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.',
+];
\ No newline at end of file
diff --git a/resources/lang/pl/pagination.php b/resources/lang/pl/pagination.php
new file mode 100644 (file)
index 0000000..5646941
--- /dev/null
@@ -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' => '&laquo; Poprzednia',
+    'next'     => 'Następna &raquo;',
+
+];
diff --git a/resources/lang/pl/passwords.php b/resources/lang/pl/passwords.php
new file mode 100644 (file)
index 0000000..a9e669f
--- /dev/null
@@ -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!',
+
+];
diff --git a/resources/lang/pl/settings.php b/resources/lang/pl/settings.php
new file mode 100644 (file)
index 0000000..381e551
--- /dev/null
@@ -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.',
+];
diff --git a/resources/lang/pl/validation.php b/resources/lang/pl/validation.php
new file mode 100644 (file)
index 0000000..6a7c13e
--- /dev/null
@@ -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' => [],
+
+];
index adfec45256db85b7d915c199a31cc34c9e442f33..9882f09a2085ab1855efddc13b6dade37d3c9901 100644 (file)
@@ -5,10 +5,10 @@
     <div class="faded-small toolbar">
         <div class="container">
             <div class="row">
-                <div class="col-sm-6 faded">
+                <div class="col-sm-6 col-xs-1  faded">
                     @include('books._breadcrumbs', ['book' => $book])
                 </div>
-                <div class="col-sm-6">
+                <div class="col-sm-6 col-xs-11">
                     <div class="action-buttons faded">
                         <span dropdown class="dropdown-container">
                             <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.export') }}</div>
     </div>
 
 
-    <div class="container" id="entity-dashboard" entity-id="{{ $book->id }}" entity-type="book">
+    <div ng-non-bindable class="container" id="entity-dashboard" entity-id="{{ $book->id }}" entity-type="book">
         <div class="row">
             <div class="col-md-7">
 
                 <h1>{{$book->name}}</h1>
                 <div class="book-content" v-if="!searching">
-                    <p class="text-muted" v-pre>{{$book->description}}</p>
+                    <p class="text-muted" v-pre>{!! nl2br(e($book->description)) !!}</p>
 
                     <div class="page-list" v-pre>
                         <hr>
                         @else
                             <p class="text-muted">{{ trans('entities.books_empty_contents') }}</p>
                             <p>
+                                @if(userCan('page-create', $book))
                                 <a href="{{ $book->getUrl('/page/create') }}" class="text-page"><i class="zmdi zmdi-file-text"></i>{{ trans('entities.books_empty_create_page') }}</a>
+                                @endif
+                                @if(userCan('page-create', $book) && userCan('chapter-create', $book))
                                 &nbsp;&nbsp;<em class="text-muted">-{{ trans('entities.books_empty_or') }}-</em>&nbsp;&nbsp;&nbsp;
+                                @endif
+                                @if(userCan('chapter-create', $book))
                                 <a href="{{ $book->getUrl('/chapter/create') }}" class="text-chapter"><i class="zmdi zmdi-collection-bookmark"></i>{{ trans('entities.books_empty_add_chapter') }}</a>
+                                @endif
                             </p>
                             <hr>
                         @endif
                 @endif
 
                 <div class="search-box">
-                    <form v-on:submit="searchBook">
+                    <form v-on:submit.prevent="searchBook">
                         <input v-model="searchTerm" v-on:change="checkSearchForm()" type="text" name="term" placeholder="{{ trans('entities.books_search_this') }}">
                         <button type="submit"><i class="zmdi zmdi-search"></i></button>
                         <button v-if="searching" v-cloak class="text-neg" v-on:click="clearSearch()" type="button"><i class="zmdi zmdi-close"></i></button>
                     </form>
                 </div>
-                
+
                 <div class="activity">
                     <h3>{{ trans('entities.recent_activity') }}</h3>
                     @include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)])
         </div>
     </div>
 
-@stop
\ No newline at end of file
+@stop
index 8487a63a3e5e4e4e20ce242b06aaeb6a192fc2ea..1ae20b301c6e794b03267ee9e8038a66218e43ca 100644 (file)
@@ -2,7 +2,7 @@
     <h4>
         @if (isset($showPath) && $showPath)
             <a href="{{ $chapter->book->getUrl() }}" class="text-book">
-                <i class="zmdi zmdi-book"></i>{{ $chapter->book->name }}
+                <i class="zmdi zmdi-book"></i>{{ $chapter->book->getShortName() }}
             </a>
             <span class="text-muted">&nbsp;&nbsp;&raquo;&nbsp;&nbsp;</span>
         @endif
index d4126cbccee764fb95fd0d27dcb84d93a2df79c0..9a319555595cda2ff105a6985c0f5f4cd4551b27 100644 (file)
@@ -5,10 +5,10 @@
     <div class="faded-small toolbar">
         <div class="container">
             <div class="row">
-                <div class="col-sm-8 faded" ng-non-bindable>
+                <div class="col-sm-6 col-xs-3 faded" ng-non-bindable>
                     @include('chapters._breadcrumbs', ['chapter' => $chapter])
                 </div>
-                <div class="col-sm-4 faded">
+                <div class="col-sm-6 col-xs-9 faded">
                     <div class="action-buttons">
                         <span dropdown class="dropdown-container">
                             <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.export') }}</div>
     </div>
 
 
-    <div class="container" id="entity-dashboard" entity-id="{{ $chapter->id }}" entity-type="chapter">
+    <div class="container" id="entity-dashboard" ng-non-bindable entity-id="{{ $chapter->id }}" entity-type="chapter">
         <div class="row">
             <div class="col-md-7">
                 <h1>{{ $chapter->name }}</h1>
                 <div class="chapter-content" v-if="!searching">
-                    <p class="text-muted">{{ $chapter->description }}</p>
+                    <p class="text-muted">{!! nl2br(e($chapter->description)) !!}</p>
 
                     @if(count($pages) > 0)
                         <div class="page-list">
                 @endif
 
                 <div class="search-box">
-                    <form v-on:submit="searchBook">
+                    <form v-on:submit.prevent="searchBook">
                         <input v-model="searchTerm" v-on:change="checkSearchForm()" type="text" name="term" placeholder="{{ trans('entities.chapters_search_this') }}">
                         <button type="submit"><i class="zmdi zmdi-search"></i></button>
                         <button v-if="searching" v-cloak class="text-neg" v-on:click="clearSearch()" type="button"><i class="zmdi zmdi-close"></i></button>
diff --git a/resources/views/components/code-editor.blade.php b/resources/views/components/code-editor.blade.php
new file mode 100644 (file)
index 0000000..5a385ef
--- /dev/null
@@ -0,0 +1,51 @@
+<div id="code-editor">
+    <div class="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="popup-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>
\ No newline at end of file
index 5ab25d1cc2110bd312d22a9539eb65c1dad956b2..6de47aaf1a31987e540d62081b471d02a84483e7 100644 (file)
@@ -21,6 +21,7 @@
     </div>
     
     @include('components.image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
+    @include('components.code-editor')
     @include('components.entity-selector-popup')
 
 @stop
\ No newline at end of file
index 70b309e7d7446b91b4996c0d104a89b706d6078c..f440a52f6afa18d8e3f26ac0e67599af259db232 100644 (file)
@@ -1,5 +1,17 @@
 <div class="page {{$page->draft ? 'draft' : ''}} entity-list-item" data-entity-type="page" data-entity-id="{{$page->id}}">
     <h4>
+        @if (isset($showPath) && $showPath)
+            <a href="{{ $page->book->getUrl() }}" class="text-book">
+                <i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}
+            </a>
+            <span class="text-muted">&nbsp;&nbsp;&raquo;&nbsp;&nbsp;</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">&nbsp;&nbsp;&raquo;&nbsp;&nbsp;</span>
+            @endif
+        @endif
         <a href="{{ $page->getUrl() }}" class="text-page entity-list-item-link"><i class="zmdi zmdi-file-text"></i><span class="entity-list-item-name">{{ $page->name }}</span></a>
     </h4>
 
index c6344434464f1ea99d21688a4da7ffa1ec9c2572..0d75a534ab0b56dc497ff1807e4dcc6454d20810 100644 (file)
@@ -5,10 +5,10 @@
     <div class="faded-small toolbar">
         <div class="container">
             <div class="row">
-                <div class="col-sm-6 faded">
+                <div class="col-sm-8 col-xs-5 faded">
                     @include('pages._breadcrumbs', ['page' => $page])
                 </div>
-                <div class="col-sm-6 faded">
+                <div class="col-sm-4 col-xs-7 faded">
                     <div class="action-buttons">
                         <span dropdown class="dropdown-container">
                             <div dropdown-toggle class="text-button text-primary"><i class="zmdi zmdi-open-in-new"></i>{{ trans('entities.export') }}</div>
index 93f2633aac12a73772fc3441f91eb56ebaf7c52f..9a146506f24729adaac8d8fe8a415e2e165600db 100644 (file)
@@ -2,7 +2,7 @@
     @if(count($entities) > 0)
         @foreach($entities as $index => $entity)
             @if($entity->isA('page'))
-                @include('pages/list-item', ['page' => $entity])
+                @include('pages/list-item', ['page' => $entity, 'showPath' => true])
             @elseif($entity->isA('book'))
                 @include('books/list-item', ['book' => $entity])
             @elseif($entity->isA('chapter'))
index 681ead91c7e7bffda43284200b00e90bf32a8c9a..8880c7b652b9b2452188c963e698f156656a082a 100644 (file)
@@ -11,6 +11,7 @@ class LdapTest extends BrowserKitTest
     public function setUp()
     {
         parent::setUp();
+        if (!defined('LDAP_OPT_REFERRALS')) define('LDAP_OPT_REFERRALS', 1);
         app('config')->set(['auth.method' => 'ldap', 'services.ldap.base_dn' => 'dc=ldap,dc=local', 'auth.providers.users.driver' => 'ldap']);
         $this->mockLdap = \Mockery::mock(\BookStack\Services\Ldap::class);
         $this->app['BookStack\Services\Ldap'] = $this->mockLdap;
@@ -21,6 +22,7 @@ class LdapTest extends BrowserKitTest
     {
         $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
         $this->mockLdap->shouldReceive('setVersion')->once();
+        $this->mockLdap->shouldReceive('setOption')->times(4);
         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(4)
             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
             ->andReturn(['count' => 1, 0 => [
@@ -49,6 +51,7 @@ class LdapTest extends BrowserKitTest
         $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
         $this->mockLdap->shouldReceive('setVersion')->once();
         $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
+        $this->mockLdap->shouldReceive('setOption')->times(2);
         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
             ->andReturn(['count' => 1, 0 => [
@@ -72,6 +75,7 @@ class LdapTest extends BrowserKitTest
     {
         $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
         $this->mockLdap->shouldReceive('setVersion')->once();
+        $this->mockLdap->shouldReceive('setOption')->times(2);
         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
             ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
             ->andReturn(['count' => 1, 0 => [
@@ -129,4 +133,4 @@ class LdapTest extends BrowserKitTest
             ->dontSee('External Authentication');
     }
 
-}
\ No newline at end of file
+}
index c665bfc231453308a0cd18d33752b834f590b71b..98259dea94b587a7792da7bea8b8c7231ce575d6 100644 (file)
@@ -1,5 +1,6 @@
 <?php namespace Tests;
 
+use BookStack\Entity;
 use BookStack\Role;
 use BookStack\Services\PermissionService;
 use Illuminate\Contracts\Console\Kernel;
@@ -117,6 +118,16 @@ abstract class BrowserKitTest extends TestCase
         ];
     }
 
+    /**
+     * Helper for updating entity permissions.
+     * @param Entity $entity
+     */
+    protected function updateEntityPermissions(Entity $entity)
+    {
+        $restrictionService = $this->app[PermissionService::class];
+        $restrictionService->buildJointPermissionsForEntity($entity);
+    }
+
     /**
      * Quick way to create a new user
      * @param array $attributes
index 94e28e94450db50de762fdde2e62383150eb5767..587430918610db0e0c6b3e1c8af44a32286cea8c 100644 (file)
@@ -1,6 +1,9 @@
 <?php namespace Tests;
 
 
+use BookStack\Chapter;
+use BookStack\Page;
+
 class EntitySearchTest extends TestCase
 {
 
@@ -75,10 +78,10 @@ class EntitySearchTest extends TestCase
             ])
         ];
 
-        $pageA = \BookStack\Page::first();
+        $pageA = Page::first();
         $pageA->tags()->saveMany($newTags);
 
-        $pageB = \BookStack\Page::all()->last();
+        $pageB = Page::all()->last();
         $pageB->tags()->create(['name' => 'animal', 'value' => 'dog']);
 
         $this->asEditor();
@@ -160,8 +163,8 @@ class EntitySearchTest extends TestCase
 
     public function test_ajax_entity_search()
     {
-        $page = \BookStack\Page::all()->last();
-        $notVisitedPage = \BookStack\Page::first();
+        $page = Page::all()->last();
+        $notVisitedPage = Page::first();
 
         // Visit the page to make popular
         $this->asEditor()->get($page->getUrl());
@@ -176,4 +179,20 @@ class EntitySearchTest extends TestCase
         $defaultListTest->assertSee($page->name);
         $defaultListTest->assertDontSee($notVisitedPage->name);
     }
+
+    public function test_ajax_entity_serach_shows_breadcrumbs()
+    {
+        $chapter = Chapter::first();
+        $page = $chapter->pages->first();
+        $this->asEditor();
+
+        $pageSearch = $this->get('/ajax/search/entities?term=' . urlencode($page->name));
+        $pageSearch->assertSee($page->name);
+        $pageSearch->assertSee($chapter->getShortName());
+        $pageSearch->assertSee($page->book->getShortName());
+
+        $chapterSearch = $this->get('/ajax/search/entities?term=' . urlencode($chapter->name));
+        $chapterSearch->assertSee($chapter->name);
+        $chapterSearch->assertSee($chapter->book->getShortName());
+    }
 }
index 0e9f691e0d86aa6d737ab211da977a5abf4cb2f1..f131ed8857927263173cdddcf0b42546b427d06b 100644 (file)
@@ -1,7 +1,10 @@
 <?php namespace Tests;
 
+use BookStack\Page;
 use BookStack\Repos\PermissionsRepo;
 use BookStack\Role;
+use Laravel\BrowserKitTesting\HttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 class RolesTest extends BrowserKitTest
 {
@@ -580,8 +583,6 @@ class RolesTest extends BrowserKitTest
             ->see('Cannot be deleted');
     }
 
-
-
     public function test_image_delete_own_permission()
     {
         $this->giveUserPermissions($this->user, ['image-update-all']);
@@ -620,6 +621,42 @@ class RolesTest extends BrowserKitTest
             ->dontSeeInDatabase('images', ['id' => $image->id]);
     }
 
+    public function test_role_permission_removal()
+    {
+        // To cover issue fixed in f99c8ff99aee9beb8c692f36d4b84dc6e651e50a.
+        $page = Page::first();
+        $viewerRole = \BookStack\Role::getRole('viewer');
+        $viewer = $this->getViewer();
+        $this->actingAs($viewer)->visit($page->getUrl())->assertResponseOk();
+
+        $this->asAdmin()->put('/settings/roles/' . $viewerRole->id, [
+            'display_name' => $viewerRole->display_name,
+            'description' => $viewerRole->description,
+            'permission' => []
+        ])->assertResponseStatus(302);
+
+        $this->expectException(HttpException::class);
+        $this->actingAs($viewer)->visit($page->getUrl())->assertResponseStatus(404);
+    }
+
+    public function test_empty_state_actions_not_visible_without_permission()
+    {
+        $admin = $this->getAdmin();
+        // Book links
+        $book = factory(\BookStack\Book::class)->create(['created_by' => $admin->id, 'updated_by' => $admin->id]);
+        $this->updateEntityPermissions($book);
+        $this->actingAs($this->getViewer())->visit($book->getUrl())
+            ->dontSee('Create a new page')
+            ->dontSee('Add a chapter');
+
+        // Chapter links
+        $chapter = factory(\BookStack\Chapter::class)->create(['created_by' => $admin->id, 'updated_by' => $admin->id, 'book_id' => $book->id]);
+        $this->updateEntityPermissions($chapter);
+        $this->actingAs($this->getViewer())->visit($chapter->getUrl())
+            ->dontSee('Create a new page')
+            ->dontSee('Sort the current book');
+    }
+
     public function test_comment_create_permission () {
         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
 
diff --git a/version b/version
index 973dcf5ace09ec3bce7c944761459a4785241871..1365ed940e528b43dbfb462c1f168179542614f9 100644 (file)
--- a/version
+++ b/version
@@ -1 +1 @@
-v0.16-dev
+v0.17-dev