diff --git a/.travis.yml b/.travis.yml index 909e3e1f4..839d3be3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 index 000000000..a17fc9523 --- /dev/null +++ b/app/Console/Commands/UpgradeDatabaseEncoding.php @@ -0,0 +1,57 @@ +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); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 4fa0b3c80..af9f5fd46 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -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 ]; /** diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 49cc15dd6..ef3ee6c48 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -23,6 +23,9 @@ class AppServiceProvider extends ServiceProvider \Blade::directive('icon', function($expression) { return ""; }); + + // Allow longer string lengths after upgrade to utf8mb4 + \Schema::defaultStringLength(191); } /** diff --git a/app/Repos/EntityRepo.php b/app/Repos/EntityRepo.php index 7bc5fc4fc..d87c40f9b 100644 --- a/app/Repos/EntityRepo.php +++ b/app/Repos/EntityRepo.php @@ -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; diff --git a/app/Services/LdapService.php b/app/Services/LdapService.php index 71dc9c0e1..598efc19d 100644 --- a/app/Services/LdapService.php +++ b/app/Services/LdapService.php @@ -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; diff --git a/app/Services/PermissionService.php b/app/Services/PermissionService.php index 89f80f936..93787a3e5 100644 --- a/app/Services/PermissionService.php +++ b/app/Services/PermissionService.php @@ -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(); } /** diff --git a/config/app.php b/config/app.php index 54cdca21b..a390eaf83 100644 --- a/config/app.php +++ b/config/app.php @@ -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'], /* |-------------------------------------------------------------------------- diff --git a/config/database.php b/config/database.php index 92c768245..3883b5868 100644 --- a/config/database.php +++ b/config/database.php @@ -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, ], diff --git a/config/services.php b/config/services.php index 99022e5f2..b4959c724 100644 --- a/config/services.php +++ b/config/services.php @@ -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_01_01_130541_create_comments_table.php b/database/migrations/2017_01_01_130541_create_comments_table.php deleted file mode 100644 index f4ece31a7..000000000 --- a/database/migrations/2017_01_01_130541_create_comments_table.php +++ /dev/null @@ -1,112 +0,0 @@ -increments('id')->unsigned(); - $table->integer('page_id')->unsigned(); - $table->longText('text')->nullable(); - $table->longText('html')->nullable(); - $table->integer('parent_id')->unsigned()->nullable(); - $table->integer('created_by')->unsigned(); - $table->integer('updated_by')->unsigned()->nullable(); - $table->index(['page_id', 'parent_id']); - $table->timestamps(); - - // Get roles with permissions we need to change - $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id; - - // Create & attach new entity permissions - $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own']; - $entity = 'Comment'; - foreach ($ops as $op) { - $permissionId = DB::table('role_permissions')->insertGetId([ - 'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)), - 'display_name' => $op . ' ' . $entity . 's', - 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), - 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() - ]); - DB::table('permission_role')->insert([ - 'role_id' => $adminRoleId, - 'permission_id' => $permissionId - ]); - } - - // Get roles with permissions we need to change - /* - $editorRole = DB::table('roles')->where('name', '=', 'editor')->first(); - if (!empty($editorRole)) { - $editorRoleId = $editorRole->id; - // Create & attach new entity permissions - $ops = ['Create All', 'Create Own', 'Update Own', 'Delete Own']; - $entity = 'Comment'; - foreach ($ops as $op) { - $permissionId = DB::table('role_permissions')->insertGetId([ - 'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)), - 'display_name' => $op . ' ' . $entity . 's', - 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), - 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() - ]); - DB::table('permission_role')->insert([ - 'role_id' => $editorRoleId, - 'permission_id' => $permissionId - ]); - } - } - - // Get roles with permissions we need to change - $viewerRole = DB::table('roles')->where('name', '=', 'viewer')->first(); - if (!empty($viewerRole)) { - $viewerRoleId = $viewerRole->id; - // Create & attach new entity permissions - $ops = ['Create All']; - $entity = 'Comment'; - foreach ($ops as $op) { - $permissionId = DB::table('role_permissions')->insertGetId([ - 'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)), - 'display_name' => $op . ' ' . $entity . 's', - 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), - 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() - ]); - DB::table('permission_role')->insert([ - 'role_id' => $viewerRoleId, - 'permission_id' => $permissionId - ]); - } - } - */ - - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('comments'); - // Create & attach new entity permissions - $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own']; - $entity = 'Comment'; - foreach ($ops as $op) { - $permName = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)); - DB::table('role_permissions')->where('name', '=', $permName)->delete(); - } - } -} diff --git a/database/migrations/2017_06_04_060012_comments_add_active_col.php b/database/migrations/2017_06_04_060012_comments_add_active_col.php deleted file mode 100644 index 3c6dd1f33..000000000 --- a/database/migrations/2017_06_04_060012_comments_add_active_col.php +++ /dev/null @@ -1,38 +0,0 @@ -boolean('active')->default(true); - $table->dropIndex('comments_page_id_parent_id_index'); - $table->index(['page_id']); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('comments', function (Blueprint $table) { - // reversing the schema - $table->dropIndex('comments_page_id_index'); - $table->dropColumn('active'); - $table->index(['page_id', 'parent_id']); - }); - } -} 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 index 000000000..5681013ad --- /dev/null +++ b/database/migrations/2017_07_02_152834_update_db_encoding_to_ut8mb4.php @@ -0,0 +1,28 @@ +increments('id')->unsigned(); + $table->integer('page_id')->unsigned(); + $table->longText('text')->nullable(); + $table->longText('html')->nullable(); + $table->integer('parent_id')->unsigned()->nullable(); + $table->integer('created_by')->unsigned(); + $table->integer('updated_by')->unsigned()->nullable(); + $table->boolean('active')->default(true); + + $table->index(['page_id']); + $table->timestamps(); + + // Assign new comment permissions to admin role + $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id; + // Create & attach new entity permissions + $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own']; + $entity = 'Comment'; + foreach ($ops as $op) { + $permissionId = DB::table('role_permissions')->insertGetId([ + 'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)), + 'display_name' => $op . ' ' . $entity . 's', + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() + ]); + DB::table('permission_role')->insert([ + 'role_id' => $adminRoleId, + 'permission_id' => $permissionId + ]); + } + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('comments'); + // Delete comment role permissions + $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own']; + $entity = 'Comment'; + foreach ($ops as $op) { + $permName = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)); + DB::table('role_permissions')->where('name', '=', $permName)->delete(); + } + } +} diff --git a/gulpfile.js b/gulpfile.js index 580db00cc..f851dd7d6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -14,8 +14,10 @@ const babelify = require("babelify"); const watchify = require("watchify"); const envify = require("envify"); const gutil = require("gulp-util"); +const liveReload = require('gulp-livereload'); if (argv.production) process.env.NODE_ENV = 'production'; +let isProduction = argv.production || process.env.NODE_ENV === 'production'; gulp.task('styles', () => { let chain = gulp.src(['resources/assets/sass/**/*.scss']) @@ -26,31 +28,40 @@ gulp.task('styles', () => { }})) .pipe(sass()) .pipe(autoprefixer('last 2 versions')); - if (argv.production) chain = chain.pipe(minifycss()); - return chain.pipe(gulp.dest('public/css/')); + if (isProduction) chain = chain.pipe(minifycss()); + return chain.pipe(gulp.dest('public/css/')).pipe(liveReload()); }); -function scriptTask(watch=false) { +function scriptTask(watch = false) { let props = { basedir: 'resources/assets/js', debug: true, - entries: ['global.js'] + entries: ['global.js'], + fast: !isProduction, + cache: {}, + packageCache: {}, }; let bundler = watch ? watchify(browserify(props), { poll: true }) : browserify(props); - bundler.transform(envify, {global: true}).transform(babelify, {presets: ['es2015']}); + + if (isProduction) { + bundler.transform(envify, {global: true}).transform(babelify, {presets: ['es2015']}); + } + function rebundle() { let stream = bundler.bundle(); stream = stream.pipe(source('common.js')); - if (argv.production) stream = stream.pipe(buffer()).pipe(uglify()); - return stream.pipe(gulp.dest('public/js/')); + if (isProduction) stream = stream.pipe(buffer()).pipe(uglify()); + return stream.pipe(gulp.dest('public/js/')).pipe(liveReload()); } + bundler.on('update', function() { rebundle(); - gutil.log('Rebundle...'); + gutil.log('Rebundling assets...'); }); + bundler.on('log', gutil.log); return rebundle(); } @@ -59,6 +70,7 @@ gulp.task('scripts', () => {scriptTask(false)}); gulp.task('scripts-watch', () => {scriptTask(true)}); gulp.task('default', ['styles', 'scripts-watch'], () => { + liveReload.listen(); gulp.watch("resources/assets/sass/**/*.scss", ['styles']); }); diff --git a/package.json b/package.json index 93f62bf1f..f447ec786 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "build": "gulp build", "production": "gulp build --production", "dev": "gulp", - "watch": "gulp" + "watch": "gulp", + "permissions": "chown -R $USER:$USER bootstrap/cache storage public/uploads" }, "devDependencies": { "babelify": "^7.3.0", @@ -13,6 +14,7 @@ "gulp": "3.9.1", "gulp-autoprefixer": "3.1.1", "gulp-clean-css": "^3.0.4", + "gulp-livereload": "^3.8.1", "gulp-minify-css": "1.2.4", "gulp-plumber": "1.1.0", "gulp-sass": "3.1.0", @@ -29,15 +31,17 @@ "angular-sanitize": "^1.5.5", "angular-ui-sortable": "^0.17.0", "axios": "^0.16.1", + "babel-polyfill": "^6.23.0", "babel-preset-es2015": "^6.24.1", - "clipboard": "^1.5.16", + "clipboard": "^1.7.1", "codemirror": "^5.26.0", "dropzone": "^4.0.1", "gulp-util": "^3.0.8", "markdown-it": "^8.3.1", "markdown-it-task-lists": "^2.0.0", "moment": "^2.12.0", - "vue": "^2.2.6" + "vue": "^2.2.6", + "vuedraggable": "^2.14.1" }, "browser": { "vue": "vue/dist/vue.common.js" diff --git a/readme.md b/readme.md index e2f16e171..5d099ad5f 100644 --- 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,17 +67,19 @@ The BookStack source is provided under the MIT License. ## Attribution -These are the great projects used to help build BookStack: +These are the great open-source projects used to help build BookStack: * [Laravel](http://laravel.com/) * [AngularJS](https://angularjs.org/) * [jQuery](https://jquery.com/) * [TinyMCE](https://www.tinymce.com/) -* [highlight.js](https://highlightjs.org/) +* [CodeMirror](https://codemirror.net) +* [Vue.js](http://vuejs.org/) +* [Axios](https://github.com/mzabriskie/axios) * [jQuery Sortable](https://johnny.github.io/jquery-sortable/) * [Material Design Iconic Font](http://zavoloklom.github.io/material-design-iconic-font/icons.html) * [Dropzone.js](http://www.dropzonejs.com/) -* [ZeroClipboard](http://zeroclipboard.org/) +* [clipboard.js](https://clipboardjs.com/) * [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html) * [markdown-it](https://github.com/markdown-it/markdown-it) and [markdown-it-task-lists](https://github.com/revin/markdown-it-task-lists) * [Moment.js](http://momentjs.com/) @@ -84,5 +89,3 @@ These are the great projects used to help build BookStack: * [Snappy (WKHTML2PDF)](https://github.com/barryvdh/laravel-snappy) * [Laravel IDE helper](https://github.com/barryvdh/laravel-ide-helper) * [WKHTMLtoPDF](http://wkhtmltopdf.org/index.html) - -Additionally, Thank you [BrowserStack](https://www.browserstack.com/) for supporting us and making cross-browser testing easy. diff --git a/resources/assets/js/code.js b/resources/assets/js/code.js index 872b13426..014c4eb77 100644 --- a/resources/assets/js/code.js +++ b/resources/assets/js/code.js @@ -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(//gi ,'\n'); - let content = codeBlocks[i].textContent; + highlightElem(codeBlocks[i]); + } +}; - CodeMirror(function(elt) { - codeBlocks[i].parentNode.replaceChild(elt, codeBlocks[i]); - }, { - value: content, - mode: "", - lineNumbers: true, - theme: 'base16-light', - readOnly: true - }); +function highlightElem(elem) { + let innerCodeElem = elem.querySelector('code[class^=language-]'); + let mode = ''; + if (innerCodeElem !== null) { + let langName = innerCodeElem.className.replace('language-', ''); + mode = getMode(langName); + } + elem.innerHTML = elem.innerHTML.replace(//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(//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"; }; diff --git a/resources/assets/js/components/back-top-top.js b/resources/assets/js/components/back-top-top.js new file mode 100644 index 000000000..5fa9b3436 --- /dev/null +++ b/resources/assets/js/components/back-top-top.js @@ -0,0 +1,53 @@ + +class BackToTop { + + constructor(elem) { + this.elem = elem; + this.targetElem = document.getElementById('header'); + this.showing = false; + this.breakPoint = 1200; + this.elem.addEventListener('click', this.scrollToTop.bind(this)); + window.addEventListener('scroll', this.onPageScroll.bind(this)); + } + + onPageScroll() { + let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0; + if (!this.showing && scrollTopPos > this.breakPoint) { + this.elem.style.display = 'block'; + this.showing = true; + setTimeout(() => { + this.elem.style.opacity = 0.4; + }, 1); + } else if (this.showing && scrollTopPos < this.breakPoint) { + this.elem.style.opacity = 0; + this.showing = false; + setTimeout(() => { + this.elem.style.display = 'none'; + }, 500); + } + } + + scrollToTop() { + let targetTop = this.targetElem.getBoundingClientRect().top; + let scrollElem = document.documentElement.scrollTop ? document.documentElement : document.body; + let duration = 300; + let start = Date.now(); + let scrollStart = this.targetElem.getBoundingClientRect().top; + + function setPos() { + let percentComplete = (1-((Date.now() - start) / duration)); + let target = Math.abs(percentComplete * scrollStart); + if (percentComplete > 0) { + scrollElem.scrollTop = target; + requestAnimationFrame(setPos.bind(this)); + } else { + scrollElem.scrollTop = targetTop; + } + } + + requestAnimationFrame(setPos.bind(this)); + } + +} + +module.exports = BackToTop; \ No newline at end of file diff --git a/resources/assets/js/components/chapter-toggle.js b/resources/assets/js/components/chapter-toggle.js new file mode 100644 index 000000000..ad373a668 --- /dev/null +++ b/resources/assets/js/components/chapter-toggle.js @@ -0,0 +1,67 @@ + +class ChapterToggle { + + constructor(elem) { + this.elem = elem; + this.isOpen = elem.classList.contains('open'); + elem.addEventListener('click', this.click.bind(this)); + } + + open() { + let list = this.elem.parentNode.querySelector('.inset-list'); + + this.elem.classList.add('open'); + list.style.display = 'block'; + list.style.height = ''; + let height = list.getBoundingClientRect().height; + list.style.height = '0px'; + list.style.overflow = 'hidden'; + list.style.transition = 'height ease-in-out 240ms'; + + let transitionEndBound = onTransitionEnd.bind(this); + function onTransitionEnd() { + list.style.overflow = ''; + list.style.height = ''; + list.style.transition = ''; + list.removeEventListener('transitionend', transitionEndBound); + } + + setTimeout(() => { + list.style.height = `${height}px`; + list.addEventListener('transitionend', transitionEndBound) + }, 1); + } + + close() { + let list = this.elem.parentNode.querySelector('.inset-list'); + + this.elem.classList.remove('open'); + list.style.display = 'block'; + list.style.height = list.getBoundingClientRect().height + 'px'; + list.style.overflow = 'hidden'; + list.style.transition = 'height ease-in-out 240ms'; + + let transitionEndBound = onTransitionEnd.bind(this); + function onTransitionEnd() { + list.style.overflow = ''; + list.style.height = ''; + list.style.transition = ''; + list.style.display = 'none'; + list.removeEventListener('transitionend', transitionEndBound); + } + + setTimeout(() => { + list.style.height = `0px`; + list.addEventListener('transitionend', transitionEndBound) + }, 1); + } + + click(event) { + event.preventDefault(); + this.isOpen ? this.close() : this.open(); + this.isOpen = !this.isOpen; + } + +} + +module.exports = ChapterToggle; \ No newline at end of file diff --git a/resources/assets/js/components/dropdown.js b/resources/assets/js/components/dropdown.js new file mode 100644 index 000000000..0401efce0 --- /dev/null +++ b/resources/assets/js/components/dropdown.js @@ -0,0 +1,48 @@ +/** + * Dropdown + * Provides some simple logic to create simple dropdown menus. + */ +class DropDown { + + constructor(elem) { + this.container = elem; + this.menu = elem.querySelector('ul'); + this.toggle = elem.querySelector('[dropdown-toggle]'); + this.setupListeners(); + } + + show() { + this.menu.style.display = 'block'; + this.menu.classList.add('anim', 'menuIn'); + this.container.addEventListener('mouseleave', this.hide.bind(this)); + + // Focus on first input if existing + let input = this.menu.querySelector('input'); + if (input !== null) input.focus(); + } + + hide() { + this.menu.style.display = 'none'; + this.menu.classList.remove('anim', 'menuIn'); + } + + setupListeners() { + // Hide menu on option click + this.container.addEventListener('click', event => { + let possibleChildren = Array.from(this.menu.querySelectorAll('a')); + if (possibleChildren.indexOf(event.target) !== -1) this.hide(); + }); + // Show dropdown on toggle click + this.toggle.addEventListener('click', this.show.bind(this)); + // Hide menu on enter press + this.container.addEventListener('keypress', event => { + if (event.keyCode !== 13) return true; + event.preventDefault(); + this.hide(); + return false; + }); + } + +} + +module.exports = DropDown; \ No newline at end of file diff --git a/resources/assets/js/components/expand-toggle.js b/resources/assets/js/components/expand-toggle.js new file mode 100644 index 000000000..61d9f54b7 --- /dev/null +++ b/resources/assets/js/components/expand-toggle.js @@ -0,0 +1,65 @@ + +class ExpandToggle { + + constructor(elem) { + this.elem = elem; + this.isOpen = false; + this.selector = elem.getAttribute('expand-toggle'); + elem.addEventListener('click', this.click.bind(this)); + } + + open(elemToToggle) { + elemToToggle.style.display = 'block'; + elemToToggle.style.height = ''; + let height = elemToToggle.getBoundingClientRect().height; + elemToToggle.style.height = '0px'; + elemToToggle.style.overflow = 'hidden'; + elemToToggle.style.transition = 'height ease-in-out 240ms'; + + let transitionEndBound = onTransitionEnd.bind(this); + function onTransitionEnd() { + elemToToggle.style.overflow = ''; + elemToToggle.style.height = ''; + elemToToggle.style.transition = ''; + elemToToggle.removeEventListener('transitionend', transitionEndBound); + } + + setTimeout(() => { + elemToToggle.style.height = `${height}px`; + elemToToggle.addEventListener('transitionend', transitionEndBound) + }, 1); + } + + close(elemToToggle) { + elemToToggle.style.display = 'block'; + elemToToggle.style.height = elemToToggle.getBoundingClientRect().height + 'px'; + elemToToggle.style.overflow = 'hidden'; + elemToToggle.style.transition = 'all ease-in-out 240ms'; + + let transitionEndBound = onTransitionEnd.bind(this); + function onTransitionEnd() { + elemToToggle.style.overflow = ''; + elemToToggle.style.height = ''; + elemToToggle.style.transition = ''; + elemToToggle.style.display = 'none'; + elemToToggle.removeEventListener('transitionend', transitionEndBound); + } + + setTimeout(() => { + elemToToggle.style.height = `0px`; + elemToToggle.addEventListener('transitionend', transitionEndBound) + }, 1); + } + + click(event) { + event.preventDefault(); + let matchingElems = document.querySelectorAll(this.selector); + for (let i = 0, len = matchingElems.length; i < len; i++) { + this.isOpen ? this.close(matchingElems[i]) : this.open(matchingElems[i]); + } + this.isOpen = !this.isOpen; + } + +} + +module.exports = ExpandToggle; \ No newline at end of file diff --git a/resources/assets/js/components/index.js b/resources/assets/js/components/index.js new file mode 100644 index 000000000..43466a0d9 --- /dev/null +++ b/resources/assets/js/components/index.js @@ -0,0 +1,28 @@ + +let componentMapping = { + 'dropdown': require('./dropdown'), + 'overlay': require('./overlay'), + 'back-to-top': require('./back-top-top'), + 'notification': require('./notification'), + 'chapter-toggle': require('./chapter-toggle'), + 'expand-toggle': require('./expand-toggle'), +}; + +window.components = {}; + +let componentNames = Object.keys(componentMapping); + +for (let i = 0, len = componentNames.length; i < len; i++) { + let name = componentNames[i]; + let elems = document.querySelectorAll(`[${name}]`); + if (elems.length === 0) continue; + + let component = componentMapping[name]; + if (typeof window.components[name] === "undefined") window.components[name] = []; + for (let j = 0, jLen = elems.length; j < jLen; j++) { + let instance = new component(elems[j]); + if (typeof elems[j].components === 'undefined') elems[j].components = {}; + elems[j].components[name] = instance; + window.components[name].push(instance); + } +} \ No newline at end of file diff --git a/resources/assets/js/components/notification.js b/resources/assets/js/components/notification.js new file mode 100644 index 000000000..1a9819702 --- /dev/null +++ b/resources/assets/js/components/notification.js @@ -0,0 +1,41 @@ + +class Notification { + + constructor(elem) { + this.elem = elem; + this.type = elem.getAttribute('notification'); + this.textElem = elem.querySelector('span'); + this.autohide = this.elem.hasAttribute('data-autohide'); + window.Events.listen(this.type, text => { + this.show(text); + }); + elem.addEventListener('click', this.hide.bind(this)); + if (elem.hasAttribute('data-show')) this.show(this.textElem.textContent); + + this.hideCleanup = this.hideCleanup.bind(this); + } + + show(textToShow = '') { + this.elem.removeEventListener('transitionend', this.hideCleanup); + this.textElem.textContent = textToShow; + this.elem.style.display = 'block'; + setTimeout(() => { + this.elem.classList.add('showing'); + }, 1); + + if (this.autohide) setTimeout(this.hide.bind(this), 2000); + } + + hide() { + this.elem.classList.remove('showing'); + this.elem.addEventListener('transitionend', this.hideCleanup); + } + + hideCleanup() { + this.elem.style.display = 'none'; + this.elem.removeEventListener('transitionend', this.hideCleanup); + } + +} + +module.exports = Notification; \ No newline at end of file diff --git a/resources/assets/js/components/overlay.js b/resources/assets/js/components/overlay.js new file mode 100644 index 000000000..6e7a598ac --- /dev/null +++ b/resources/assets/js/components/overlay.js @@ -0,0 +1,39 @@ + +class Overlay { + + constructor(elem) { + this.container = elem; + elem.addEventListener('click', event => { + if (event.target === elem) return this.hide(); + }); + let closeButtons = elem.querySelectorAll('.overlay-close'); + for (let i=0; i < closeButtons.length; i++) { + closeButtons[i].addEventListener('click', this.hide.bind(this)); + } + } + + toggle(show = true) { + let start = Date.now(); + let duration = 240; + + function setOpacity() { + let elapsedTime = (Date.now() - start); + let targetOpacity = show ? (elapsedTime / duration) : 1-(elapsedTime / duration); + this.container.style.opacity = targetOpacity; + if (elapsedTime > duration) { + this.container.style.display = show ? 'flex' : 'none'; + this.container.style.opacity = ''; + } else { + requestAnimationFrame(setOpacity.bind(this)); + } + } + + requestAnimationFrame(setOpacity.bind(this)); + } + + hide() { this.toggle(false); } + show() { this.toggle(true); } + +} + +module.exports = Overlay; \ No newline at end of file diff --git a/resources/assets/js/controllers.js b/resources/assets/js/controllers.js index aebde8da4..132580f68 100644 --- a/resources/assets/js/controllers.js +++ b/resources/assets/js/controllers.js @@ -8,256 +8,6 @@ moment.locale('en-gb'); module.exports = function (ngApp, events) { - ngApp.controller('ImageManagerController', ['$scope', '$attrs', '$http', '$timeout', 'imageManagerService', - function ($scope, $attrs, $http, $timeout, imageManagerService) { - - $scope.images = []; - $scope.imageType = $attrs.imageType; - $scope.selectedImage = false; - $scope.dependantPages = false; - $scope.showing = false; - $scope.hasMore = false; - $scope.imageUpdateSuccess = false; - $scope.imageDeleteSuccess = false; - $scope.uploadedTo = $attrs.uploadedTo; - $scope.view = 'all'; - - $scope.searching = false; - $scope.searchTerm = ''; - - let page = 0; - let previousClickTime = 0; - let previousClickImage = 0; - let dataLoaded = false; - let callback = false; - - let preSearchImages = []; - let preSearchHasMore = false; - - /** - * Used by dropzone to get the endpoint to upload to. - * @returns {string} - */ - $scope.getUploadUrl = function () { - return window.baseUrl('/images/' + $scope.imageType + '/upload'); - }; - - /** - * Cancel the current search operation. - */ - function cancelSearch() { - $scope.searching = false; - $scope.searchTerm = ''; - $scope.images = preSearchImages; - $scope.hasMore = preSearchHasMore; - } - $scope.cancelSearch = cancelSearch; - - - /** - * Runs on image upload, Adds an image to local list of images - * and shows a success message to the user. - * @param file - * @param data - */ - $scope.uploadSuccess = function (file, data) { - $scope.$apply(() => { - $scope.images.unshift(data); - }); - events.emit('success', trans('components.image_upload_success')); - }; - - /** - * Runs the callback and hides the image manager. - * @param returnData - */ - function callbackAndHide(returnData) { - if (callback) callback(returnData); - $scope.hide(); - } - - /** - * Image select action. Checks if a double-click was fired. - * @param image - */ - $scope.imageSelect = function (image) { - let dblClickTime = 300; - let currentTime = Date.now(); - let timeDiff = currentTime - previousClickTime; - - if (timeDiff < dblClickTime && image.id === previousClickImage) { - // If double click - callbackAndHide(image); - } else { - // If single - $scope.selectedImage = image; - $scope.dependantPages = false; - } - previousClickTime = currentTime; - previousClickImage = image.id; - }; - - /** - * Action that runs when the 'Select image' button is clicked. - * Runs the callback and hides the image manager. - */ - $scope.selectButtonClick = function () { - callbackAndHide($scope.selectedImage); - }; - - /** - * Show the image manager. - * Takes a callback to execute later on. - * @param doneCallback - */ - function show(doneCallback) { - callback = doneCallback; - $scope.showing = true; - $('#image-manager').find('.overlay').css('display', 'flex').hide().fadeIn(240); - // Get initial images if they have not yet been loaded in. - if (!dataLoaded) { - fetchData(); - dataLoaded = true; - } - } - - // Connects up the image manger so it can be used externally - // such as from TinyMCE. - imageManagerService.show = show; - imageManagerService.showExternal = function (doneCallback) { - $scope.$apply(() => { - show(doneCallback); - }); - }; - window.ImageManager = imageManagerService; - - /** - * Hide the image manager - */ - $scope.hide = function () { - $scope.showing = false; - $('#image-manager').find('.overlay').fadeOut(240); - }; - - let baseUrl = window.baseUrl('/images/' + $scope.imageType + '/all/'); - - /** - * Fetch the list image data from the server. - */ - function fetchData() { - let url = baseUrl + page + '?'; - let components = {}; - if ($scope.uploadedTo) components['page_id'] = $scope.uploadedTo; - if ($scope.searching) components['term'] = $scope.searchTerm; - - - url += Object.keys(components).map((key) => { - return key + '=' + encodeURIComponent(components[key]); - }).join('&'); - - $http.get(url).then((response) => { - $scope.images = $scope.images.concat(response.data.images); - $scope.hasMore = response.data.hasMore; - page++; - }); - } - $scope.fetchData = fetchData; - - /** - * Start a search operation - */ - $scope.searchImages = function() { - - if ($scope.searchTerm === '') { - cancelSearch(); - return; - } - - if (!$scope.searching) { - preSearchImages = $scope.images; - preSearchHasMore = $scope.hasMore; - } - - $scope.searching = true; - $scope.images = []; - $scope.hasMore = false; - page = 0; - baseUrl = window.baseUrl('/images/' + $scope.imageType + '/search/'); - fetchData(); - }; - - /** - * Set the current image listing view. - * @param viewName - */ - $scope.setView = function(viewName) { - cancelSearch(); - $scope.images = []; - $scope.hasMore = false; - page = 0; - $scope.view = viewName; - baseUrl = window.baseUrl('/images/' + $scope.imageType + '/' + viewName + '/'); - fetchData(); - }; - - /** - * Save the details of an image. - * @param event - */ - $scope.saveImageDetails = function (event) { - event.preventDefault(); - let url = window.baseUrl('/images/update/' + $scope.selectedImage.id); - $http.put(url, this.selectedImage).then(response => { - events.emit('success', trans('components.image_update_success')); - }, (response) => { - if (response.status === 422) { - let errors = response.data; - let message = ''; - Object.keys(errors).forEach((key) => { - message += errors[key].join('\n'); - }); - events.emit('error', message); - } else if (response.status === 403) { - events.emit('error', response.data.error); - } - }); - }; - - /** - * Delete an image from system and notify of success. - * Checks if it should force delete when an image - * has dependant pages. - * @param event - */ - $scope.deleteImage = function (event) { - event.preventDefault(); - let force = $scope.dependantPages !== false; - let url = window.baseUrl('/images/' + $scope.selectedImage.id); - if (force) url += '?force=true'; - $http.delete(url).then((response) => { - $scope.images.splice($scope.images.indexOf($scope.selectedImage), 1); - $scope.selectedImage = false; - events.emit('success', trans('components.image_delete_success')); - }, (response) => { - // Pages failure - if (response.status === 400) { - $scope.dependantPages = response.data; - } else if (response.status === 403) { - events.emit('error', response.data.error); - } - }); - }; - - /** - * Simple date creator used to properly format dates. - * @param stringDate - * @returns {Date} - */ - $scope.getDate = function (stringDate) { - return new Date(stringDate); - }; - - }]); ngApp.controller('PageEditController', ['$scope', '$http', '$attrs', '$interval', '$timeout', '$sce', function ($scope, $http, $attrs, $interval, $timeout, $sce) { @@ -370,14 +120,8 @@ module.exports = function (ngApp, events) { saveDraft(); }; - // Listen to shortcuts coming via events - $scope.$on('editor-keydown', (event, data) => { - // Save shortcut (ctrl+s) - if (data.keyCode == 83 && (navigator.platform.match("Mac") ? data.metaKey : data.ctrlKey)) { - data.preventDefault(); - saveDraft(); - } - }); + // Listen to save draft events from editor + $scope.$on('save-draft', saveDraft); /** * Discard the current draft and grab the current page @@ -385,7 +129,7 @@ module.exports = function (ngApp, events) { */ $scope.discardDraft = function () { let url = window.baseUrl('/ajax/page/' + pageId); - $http.get(url).then((responseData) => { + $http.get(url).then(responseData => { if (autoSave) $interval.cancel(autoSave); $scope.draftText = trans('entities.pages_editing_page'); $scope.isUpdateDraft = false; @@ -401,90 +145,6 @@ module.exports = function (ngApp, events) { }]); - ngApp.controller('PageTagController', ['$scope', '$http', '$attrs', - function ($scope, $http, $attrs) { - - const pageId = Number($attrs.pageId); - $scope.tags = []; - - $scope.sortOptions = { - handle: '.handle', - items: '> tr', - containment: "parent", - axis: "y" - }; - - /** - * Push an empty tag to the end of the scope tags. - */ - function addEmptyTag() { - $scope.tags.push({ - name: '', - value: '' - }); - } - $scope.addEmptyTag = addEmptyTag; - - /** - * Get all tags for the current book and add into scope. - */ - function getTags() { - let url = window.baseUrl(`/ajax/tags/get/page/${pageId}`); - $http.get(url).then((responseData) => { - $scope.tags = responseData.data; - addEmptyTag(); - }); - } - getTags(); - - /** - * Set the order property on all tags. - */ - function setTagOrder() { - for (let i = 0; i < $scope.tags.length; i++) { - $scope.tags[i].order = i; - } - } - - /** - * When an tag changes check if another empty editable - * field needs to be added onto the end. - * @param tag - */ - $scope.tagChange = function(tag) { - let cPos = $scope.tags.indexOf(tag); - if (cPos !== $scope.tags.length-1) return; - - if (tag.name !== '' || tag.value !== '') { - addEmptyTag(); - } - }; - - /** - * When an tag field loses focus check the tag to see if its - * empty and therefore could be removed from the list. - * @param tag - */ - $scope.tagBlur = function(tag) { - let isLast = $scope.tags.length - 1 === $scope.tags.indexOf(tag); - if (tag.name === '' && tag.value === '' && !isLast) { - let cPos = $scope.tags.indexOf(tag); - $scope.tags.splice(cPos, 1); - } - }; - - /** - * Remove a tag from the current list. - * @param tag - */ - $scope.removeTag = function(tag) { - let cIndex = $scope.tags.indexOf(tag); - $scope.tags.splice(cIndex, 1); - }; - - }]); - - ngApp.controller('PageAttachmentController', ['$scope', '$http', '$attrs', function ($scope, $http, $attrs) { diff --git a/resources/assets/js/directives.js b/resources/assets/js/directives.js index 16d1ad2a4..2a0547c97 100644 --- a/resources/assets/js/directives.js +++ b/resources/assets/js/directives.js @@ -114,39 +114,6 @@ module.exports = function (ngApp, events) { }; }]); - /** - * Dropdown - * Provides some simple logic to create small dropdown menus - */ - ngApp.directive('dropdown', [function () { - return { - restrict: 'A', - link: function (scope, element, attrs) { - const menu = element.find('ul'); - element.find('[dropdown-toggle]').on('click', function () { - menu.show().addClass('anim menuIn'); - let inputs = menu.find('input'); - let hasInput = inputs.length > 0; - if (hasInput) { - inputs.first().focus(); - element.on('keypress', 'input', event => { - if (event.keyCode === 13) { - event.preventDefault(); - menu.hide(); - menu.removeClass('anim menuIn'); - return false; - } - }); - } - element.mouseleave(function () { - menu.hide(); - menu.removeClass('anim menuIn'); - }); - }); - } - }; - }]); - /** * TinyMCE * An angular wrapper around the tinyMCE editor. @@ -187,30 +154,6 @@ module.exports = function (ngApp, events) { } scope.tinymce.extraSetups.push(tinyMceSetup); - - // Custom tinyMCE plugins - tinymce.PluginManager.add('customhr', function (editor) { - editor.addCommand('InsertHorizontalRule', function () { - let hrElem = document.createElement('hr'); - let cNode = editor.selection.getNode(); - let parentNode = cNode.parentNode; - parentNode.insertBefore(hrElem, cNode); - }); - - editor.addButton('hr', { - icon: 'hr', - tooltip: 'Horizontal line', - cmd: 'InsertHorizontalRule' - }); - - editor.addMenuItem('hr', { - icon: 'hr', - text: 'Horizontal line', - cmd: 'InsertHorizontalRule', - context: 'insert' - }); - }); - tinymce.init(scope.tinymce); } } @@ -232,15 +175,48 @@ module.exports = function (ngApp, events) { }, link: function (scope, element, attrs) { - // Set initial model content - element = element.find('textarea').first(); - // Codemirror Setup + element = element.find('textarea').first(); let cm = code.markdownEditor(element[0]); + + // Custom key commands + let metaKey = code.getMetaKey(); + const extraKeys = {}; + // Insert Image shortcut + extraKeys[`${metaKey}-Alt-I`] = function(cm) { + let selectedText = cm.getSelection(); + let newText = `![${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('

', '');}; + cm.setOption('extraKeys', extraKeys); + + // Update data on content change cm.on('change', (instance, changeObj) => { update(instance); }); + // Handle scroll to sync display view cm.on('scroll', instance => { // Thanks to http://liuhao.im/english/2015/11/10/the-sync-scroll-of-markdown-editor-in-javascript.html let scroll = instance.getScrollInfo(); @@ -257,6 +233,166 @@ module.exports = function (ngApp, events) { scope.$emit('markdown-scroll', totalLines.length); }); + // Handle image paste + cm.on('paste', (cm, event) => { + if (!event.clipboardData || !event.clipboardData.items) return; + for (let i = 0; i < event.clipboardData.items.length; i++) { + uploadImage(event.clipboardData.items[i].getAsFile()); + } + }); + + // Handle images on drag-drop + cm.on('drop', (cm, event) => { + event.stopPropagation(); + event.preventDefault(); + let cursorPos = cm.coordsChar({left: event.pageX, top: event.pageY}); + cm.setCursor(cursorPos); + if (!event.dataTransfer || !event.dataTransfer.files) return; + for (let i = 0; i < event.dataTransfer.files.length; i++) { + uploadImage(event.dataTransfer.files[i]); + } + }); + + // Helper to replace editor content + function replaceContent(search, replace) { + let text = cm.getValue(); + let cursor = cm.listSelections(); + cm.setValue(text.replace(search, replace)); + cm.setSelections(cursor); + } + + // Helper to replace the start of the line + function replaceLineStart(newStart) { + let cursor = cm.getCursor(); + let lineContent = cm.getLine(cursor.line); + let lineLen = lineContent.length; + let lineStart = lineContent.split(' ')[0]; + + // Remove symbol if already set + if (lineStart === newStart) { + lineContent = lineContent.replace(`${newStart} `, ''); + cm.replaceRange(lineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen}); + cm.setCursor({line: cursor.line, ch: cursor.ch - (newStart.length + 1)}); + return; + } + + let alreadySymbol = /^[#>`]/.test(lineStart); + let posDif = 0; + if (alreadySymbol) { + posDif = newStart.length - lineStart.length; + lineContent = lineContent.replace(lineStart, newStart).trim(); + } else if (newStart !== '') { + posDif = newStart.length + 1; + lineContent = newStart + ' ' + lineContent; + } + cm.replaceRange(lineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen}); + cm.setCursor({line: cursor.line, ch: cursor.ch + posDif}); + } + + function wrapLine(start, end) { + let cursor = cm.getCursor(); + let lineContent = cm.getLine(cursor.line); + let lineLen = lineContent.length; + let newLineContent = lineContent; + + if (lineContent.indexOf(start) === 0 && lineContent.slice(-end.length) === end) { + newLineContent = lineContent.slice(start.length, lineContent.length - end.length); + } else { + newLineContent = `${start}${lineContent}${end}`; + } + + cm.replaceRange(newLineContent, {line: cursor.line, ch: 0}, {line: cursor.line, ch: lineLen}); + cm.setCursor({line: cursor.line, ch: cursor.ch + (newLineContent.length - lineLen)}); + } + + function wrapSelection(start, end) { + let selection = cm.getSelection(); + if (selection === '') return wrapLine(start, end); + let newSelection = selection; + let frontDiff = 0; + let endDiff = 0; + + if (selection.indexOf(start) === 0 && selection.slice(-end.length) === end) { + newSelection = selection.slice(start.length, selection.length - end.length); + endDiff = -(end.length + start.length); + } else { + newSelection = `${start}${selection}${end}`; + endDiff = start.length + end.length; + } + + let selections = cm.listSelections()[0]; + cm.replaceSelection(newSelection); + let headFirst = selections.head.ch <= selections.anchor.ch; + selections.head.ch += headFirst ? frontDiff : endDiff; + selections.anchor.ch += headFirst ? endDiff : frontDiff; + cm.setSelections([selections]); + } + + // Handle image upload and add image into markdown content + function uploadImage(file) { + if (file === null || file.type.indexOf('image') !== 0) return; + let ext = 'png'; + + if (file.name) { + let fileNameMatches = file.name.match(/\.(.+)$/); + if (fileNameMatches.length > 1) ext = fileNameMatches[1]; + } + + // Insert image into markdown + let id = "image-" + Math.random().toString(16).slice(2); + let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`); + let selectedText = cm.getSelection(); + let placeHolderText = `![${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.show(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 +403,9 @@ module.exports = function (ngApp, events) { } update(cm); + // Listen to commands from parent scope + scope.$on('md-insert-link', showLinkSelector); + scope.$on('md-insert-image', showImageManager); scope.$on('markdown-update', (event, value) => { cm.setValue(value); element.val(value); @@ -287,8 +426,7 @@ module.exports = function (ngApp, events) { restrict: 'A', link: function (scope, element, attrs) { - // Elements - const $input = element.find('[markdown-input] textarea').first(); + // Editor Elements const $display = element.find('.markdown-display').first(); const $insertImage = element.find('button[data-action="insertImage"]'); const $insertEntityLink = element.find('button[data-action="insertEntityLink"]'); @@ -299,11 +437,9 @@ module.exports = function (ngApp, events) { window.open(this.getAttribute('href')); }); - let currentCaretPos = 0; - - $input.blur(event => { - currentCaretPos = $input[0].selectionStart; - }); + // Editor UI Actions + $insertEntityLink.click(e => {scope.$broadcast('md-insert-link');}); + $insertImage.click(e => {scope.$broadcast('md-insert-image');}); // Handle scroll sync event from editor scroll $rootScope.$on('markdown-scroll', (event, lineCount) => { @@ -315,140 +451,6 @@ module.exports = function (ngApp, events) { }, {queue: false, duration: 200, easing: 'linear'}); } }); - - // Editor key-presses - $input.keydown(event => { - // Insert image shortcut - if (event.which === 73 && event.ctrlKey && event.shiftKey) { - event.preventDefault(); - let caretPos = $input[0].selectionStart; - let currentContent = $input.val(); - const mdImageText = "![](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); - } - } } }]); @@ -494,188 +496,6 @@ module.exports = function (ngApp, events) { } }]); - /** - * Tag Autosuggestions - * Listens to child inputs and provides autosuggestions depending on field type - * and input. Suggestions provided by server. - */ - ngApp.directive('tagAutosuggestions', ['$http', function ($http) { - return { - restrict: 'A', - link: function (scope, elem, attrs) { - - // Local storage for quick caching. - const localCache = {}; - - // Create suggestion element - const suggestionBox = document.createElement('ul'); - suggestionBox.className = 'suggestion-box'; - suggestionBox.style.position = 'absolute'; - suggestionBox.style.display = 'none'; - const $suggestionBox = $(suggestionBox); - - // General state tracking - let isShowing = false; - let currentInput = false; - let active = 0; - - // Listen to input events on autosuggest fields - elem.on('input focus', '[autosuggest]', function (event) { - let $input = $(this); - let val = $input.val(); - let url = $input.attr('autosuggest'); - let type = $input.attr('autosuggest-type'); - - // Add name param to request if for a value - if (type.toLowerCase() === 'value') { - let $nameInput = $input.closest('tr').find('[autosuggest-type="name"]').first(); - let nameVal = $nameInput.val(); - if (nameVal !== '') { - url += '?name=' + encodeURIComponent(nameVal); - } - } - - let suggestionPromise = getSuggestions(val.slice(0, 3), url); - suggestionPromise.then(suggestions => { - if (val.length === 0) { - displaySuggestions($input, suggestions.slice(0, 6)); - } else { - suggestions = suggestions.filter(item => { - return item.toLowerCase().indexOf(val.toLowerCase()) !== -1; - }).slice(0, 4); - displaySuggestions($input, suggestions); - } - }); - }); - - // Hide autosuggestions when input loses focus. - // Slight delay to allow clicks. - let lastFocusTime = 0; - elem.on('blur', '[autosuggest]', function (event) { - let startTime = Date.now(); - setTimeout(() => { - if (lastFocusTime < startTime) { - $suggestionBox.hide(); - isShowing = false; - } - }, 200) - }); - elem.on('focus', '[autosuggest]', function (event) { - lastFocusTime = Date.now(); - }); - - elem.on('keydown', '[autosuggest]', function (event) { - if (!isShowing) return; - - let suggestionElems = suggestionBox.childNodes; - let suggestCount = suggestionElems.length; - - // Down arrow - if (event.keyCode === 40) { - let newActive = (active === suggestCount - 1) ? 0 : active + 1; - changeActiveTo(newActive, suggestionElems); - } - // Up arrow - else if (event.keyCode === 38) { - let newActive = (active === 0) ? suggestCount - 1 : active - 1; - changeActiveTo(newActive, suggestionElems); - } - // Enter or tab key - else if ((event.keyCode === 13 || event.keyCode === 9) && !event.shiftKey) { - currentInput[0].value = suggestionElems[active].textContent; - currentInput.focus(); - $suggestionBox.hide(); - isShowing = false; - if (event.keyCode === 13) { - event.preventDefault(); - return false; - } - } - }); - - // Change the active suggestion to the given index - function changeActiveTo(index, suggestionElems) { - suggestionElems[active].className = ''; - active = index; - suggestionElems[active].className = 'active'; - } - - // Display suggestions on a field - let prevSuggestions = []; - - function displaySuggestions($input, suggestions) { - - // Hide if no suggestions - if (suggestions.length === 0) { - $suggestionBox.hide(); - isShowing = false; - prevSuggestions = suggestions; - return; - } - - // Otherwise show and attach to input - if (!isShowing) { - $suggestionBox.show(); - isShowing = true; - } - if ($input !== currentInput) { - $suggestionBox.detach(); - $input.after($suggestionBox); - currentInput = $input; - } - - // Return if no change - if (prevSuggestions.join() === suggestions.join()) { - prevSuggestions = suggestions; - return; - } - - // Build suggestions - $suggestionBox[0].innerHTML = ''; - for (let i = 0; i < suggestions.length; i++) { - let suggestion = document.createElement('li'); - suggestion.textContent = suggestions[i]; - suggestion.onclick = suggestionClick; - if (i === 0) { - suggestion.className = 'active'; - active = 0; - } - $suggestionBox[0].appendChild(suggestion); - } - - prevSuggestions = suggestions; - } - - // Suggestion click event - function suggestionClick(event) { - currentInput[0].value = this.textContent; - currentInput.focus(); - $suggestionBox.hide(); - isShowing = false; - } - - // Get suggestions & cache - function getSuggestions(input, url) { - let hasQuery = url.indexOf('?') !== -1; - let searchUrl = url + (hasQuery ? '&' : '?') + 'search=' + encodeURIComponent(input); - - // Get from local cache if exists - if (typeof localCache[searchUrl] !== 'undefined') { - return new Promise((resolve, reject) => { - resolve(localCache[searchUrl]); - }); - } - - return $http.get(searchUrl).then(response => { - localCache[searchUrl] = response.data; - return response.data; - }); - } - - } - } - }]); - ngApp.directive('entityLinkSelector', [function($http) { return { restrict: 'A', @@ -711,6 +531,7 @@ module.exports = function (ngApp, events) { function hide() { element.fadeOut(240); } + scope.hide = hide; // Listen to confirmation of entity selections (doubleclick) events.listen('entity-select-confirm', entity => { diff --git a/resources/assets/js/global.js b/resources/assets/js/global.js index dc6802e12..28d1e3b0c 100644 --- a/resources/assets/js/global.js +++ b/resources/assets/js/global.js @@ -1,4 +1,5 @@ "use strict"; +require("babel-polyfill"); // Url retrieval function window.baseUrl = function(path) { @@ -17,11 +18,9 @@ let axiosInstance = axios.create({ 'baseURL': window.baseUrl('') } }); - +window.$http = axiosInstance; Vue.prototype.$http = axiosInstance; -require("./vues/vues"); - // AngularJS - Create application and load components const angular = require("angular"); @@ -64,11 +63,12 @@ class EventManager { window.Events = new EventManager(); Vue.prototype.$events = window.Events; +require("./vues/vues"); +require("./components"); + // Load in angular specific items -const Services = require('./services'); const Directives = require('./directives'); const Controllers = require('./controllers'); -Services(ngApp, window.Events); Directives(ngApp, window.Events); Controllers(ngApp, window.Events); @@ -90,83 +90,11 @@ jQuery.expr[":"].contains = $.expr.createPseudo(function (arg) { }; }); -// Global jQuery Elements -let notifications = $('.notification'); -let successNotification = notifications.filter('.pos'); -let errorNotification = notifications.filter('.neg'); -let warningNotification = notifications.filter('.warning'); -// Notification Events -window.Events.listen('success', function (text) { - successNotification.hide(); - successNotification.find('span').text(text); - setTimeout(() => { - successNotification.show(); - }, 1); -}); -window.Events.listen('warning', function (text) { - warningNotification.find('span').text(text); - warningNotification.show(); -}); -window.Events.listen('error', function (text) { - errorNotification.find('span').text(text); - errorNotification.show(); -}); - -// Notification hiding -notifications.click(function () { - $(this).fadeOut(100); -}); - -// Chapter page list toggles -$('.chapter-toggle').click(function (e) { - e.preventDefault(); - $(this).toggleClass('open'); - $(this).closest('.chapter').find('.inset-list').slideToggle(180); -}); - -// Back to top button -$('#back-to-top').click(function() { - $('#header').smoothScrollTo(); -}); -let scrollTopShowing = false; -let scrollTop = document.getElementById('back-to-top'); -let scrollTopBreakpoint = 1200; -window.addEventListener('scroll', function() { - let scrollTopPos = document.documentElement.scrollTop || document.body.scrollTop || 0; - if (!scrollTopShowing && scrollTopPos > scrollTopBreakpoint) { - scrollTop.style.display = 'block'; - scrollTopShowing = true; - setTimeout(() => { - scrollTop.style.opacity = 0.4; - }, 1); - } else if (scrollTopShowing && scrollTopPos < scrollTopBreakpoint) { - scrollTop.style.opacity = 0; - scrollTopShowing = false; - setTimeout(() => { - scrollTop.style.display = 'none'; - }, 500); - } -}); - -// Common jQuery actions -$('[data-action="expand-entity-list-details"]').click(function() { - $('.entity-list.compact').find('p').not('.empty-text').slideToggle(240); -}); - -// Popup close -$('.popup-close').click(function() { - $(this).closest('.overlay').fadeOut(240); -}); -$('.overlay').click(function(event) { - if (!$(event.target).hasClass('overlay')) return; - $(this).fadeOut(240); -}); - // Detect IE for css if(navigator.userAgent.indexOf('MSIE')!==-1 || navigator.appVersion.indexOf('Trident/') > 0 || navigator.userAgent.indexOf('Safari') !== -1){ - $('body').addClass('flexbox-support'); + document.body.classList.add('flexbox-support'); } // Page specific items diff --git a/resources/assets/js/pages/page-form.js b/resources/assets/js/pages/page-form.js index 04951b174..08e4c0c34 100644 --- a/resources/assets/js/pages/page-form.js +++ b/resources/assets/js/pages/page-form.js @@ -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 = `

`; + 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.append($('').each((index, elem) => {
+            // Needs to be textContent since innerText produces BR:s
+            elem.textContent = code;
+        }).attr('class', `language-${lang}`));
+        $codeMirrorContainer.replaceWith($pre);
+    }
+
+    window.tinymce.PluginManager.add('codeeditor', function(editor, url) {
+
+        let $ = editor.$;
+
+        editor.addButton('codeeditor', {
+            text: 'Code block',
+            icon: false,
+            cmd: 'codeeditor'
+        });
+
+        editor.addCommand('codeeditor', () => {
+            showPopup(editor);
+        });
+
+        // Convert
+        editor.on('PreProcess', function (e) {
+            $('div.CodeMirrorContainer', e.node).
+            each((index, elem) => {
+                let $elem = $(elem);
+                codeMirrorContainerToPre($elem);
+            });
+        });
+
+        editor.on('dblclick', event => {
+            let selectedNode = editor.selection.getNode();
+            if (!elemIsCodeBlock(selectedNode)) return;
+            showPopup(editor);
+        });
+
+        editor.on('SetContent', function () {
+
+            // Recover broken codemirror instances
+            $('.CodeMirrorContainer').filter((index ,elem) => {
+                return typeof elem.querySelector('.CodeMirror').CodeMirror === 'undefined';
+            }).each((index, elem) => {
+                codeMirrorContainerToPre($(elem));
+            });
+
+            let codeSamples = $('body > pre').filter((index, elem) => {
+                return elem.contentEditable !== "false";
+            });
+
+            if (!codeSamples.length) return;
+            editor.undoManager.transact(function () {
+                codeSamples.each((index, elem) => {
+                    Code.wysiwygView(elem);
+                });
+            });
+        });
+
+    });
+}
+
+function hrPlugin() {
+    window.tinymce.PluginManager.add('customhr', function (editor) {
+        editor.addCommand('InsertHorizontalRule', function () {
+            let hrElem = document.createElement('hr');
+            let cNode = editor.selection.getNode();
+            let parentNode = cNode.parentNode;
+            parentNode.insertBefore(hrElem, cNode);
+        });
+
+        editor.addButton('hr', {
+            icon: 'hr',
+            tooltip: 'Horizontal line',
+            cmd: 'InsertHorizontalRule'
+        });
+
+        editor.addMenuItem('hr', {
+            icon: 'hr',
+            text: 'Horizontal line',
+            cmd: 'InsertHorizontalRule',
+            context: 'insert'
+        });
+    });
 }
 
 module.exports = function() {
+    hrPlugin();
+    codePlugin();
     let settings = {
         selector: '#html-editor',
         content_css: [
             window.baseUrl('/css/styles.css'),
             window.baseUrl('/libs/material-design-iconic-font/css/material-design-iconic-font.min.css')
         ],
+        branding: false,
         body_class: 'page-content',
         browser_spellcheck: true,
         relative_urls: false,
@@ -77,10 +239,10 @@ module.exports = function() {
         paste_data_images: false,
         extended_valid_elements: 'pre[*]',
         automatic_uploads: false,
-        valid_children: "-div[p|pre|h1|h2|h3|h4|h5|h6|blockquote]",
-        plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codesample",
+        valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre]",
+        plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor",
         imagetools_toolbar: 'imageoptions',
-        toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen codesample",
+        toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen",
         content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}",
         style_formats: [
             {title: "Header Large", format: "h2"},
@@ -89,20 +251,25 @@ module.exports = function() {
             {title: "Header Tiny", format: "h5"},
             {title: "Paragraph", format: "p", exact: true, classes: ''},
             {title: "Blockquote", format: "blockquote"},
-            {title: "Code Block", icon: "code", format: "pre"},
+            {title: "Code Block", icon: "code", cmd: 'codeeditor', format: 'codeeditor'},
             {title: "Inline Code", icon: "code", inline: "code"},
             {title: "Callouts", items: [
-                {title: "Success", block: 'p', exact: true, attributes : {'class' : 'callout success'}},
-                {title: "Info", block: 'p', exact: true, attributes : {'class' : 'callout info'}},
-                {title: "Warning", block: 'p', exact: true, attributes : {'class' : 'callout warning'}},
-                {title: "Danger", block: 'p', exact: true, attributes : {'class' : 'callout danger'}}
-            ]}
+                {title: "Info", format: 'calloutinfo'},
+                {title: "Success", format: 'calloutsuccess'},
+                {title: "Warning", format: 'calloutwarning'},
+                {title: "Danger", format: 'calloutdanger'}
+            ]},
         ],
         style_formats_merge: false,
         formats: {
+            codeeditor: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div'},
             alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-left'},
             aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-center'},
             alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'},
+            calloutsuccess: {block: 'p', exact: true, attributes: {class: 'callout success'}},
+            calloutinfo: {block: 'p', exact: true, attributes: {class: 'callout info'}},
+            calloutwarning: {block: 'p', exact: true, attributes: {class: 'callout warning'}},
+            calloutdanger: {block: 'p', exact: true, attributes: {class: 'callout danger'}}
         },
         file_browser_callback: function (field_name, url, type, win) {
 
@@ -116,7 +283,7 @@ module.exports = function() {
 
             if (type === 'image') {
                 // Show image manager
-                window.ImageManager.showExternal(function (image) {
+                window.ImageManager.show(function (image) {
 
                     // Set popover link input to image url then fire change event
                     // to ensure the new value sticks
@@ -198,7 +365,7 @@ module.exports = function() {
                 icon: 'image',
                 tooltip: 'Insert an image',
                 onclick: function () {
-                    window.ImageManager.showExternal(function (image) {
+                    window.ImageManager.show(function (image) {
                         let html = ``;
                         html += `${image.name}`;
                         html += '';
diff --git a/resources/assets/js/pages/page-show.js b/resources/assets/js/pages/page-show.js
index 020229d2f..7754840af 100644
--- a/resources/assets/js/pages/page-show.js
+++ b/resources/assets/js/pages/page-show.js
@@ -1,5 +1,3 @@
-"use strict";
-// Configure ZeroClipboard
 const Clipboard = require("clipboard");
 const Code = require('../code');
 
diff --git a/resources/assets/js/services.js b/resources/assets/js/services.js
deleted file mode 100644
index cd2759c54..000000000
--- a/resources/assets/js/services.js
+++ /dev/null
@@ -1,12 +0,0 @@
-"use strict";
-
-module.exports = function(ngApp, events) {
-
-    ngApp.factory('imageManagerService', function() {
-        return {
-            show: false,
-            showExternal: false
-        };
-    });
-
-};
\ No newline at end of file
diff --git a/resources/assets/js/vues/code-editor.js b/resources/assets/js/vues/code-editor.js
new file mode 100644
index 000000000..35a98cc77
--- /dev/null
+++ b/resources/assets/js/vues/code-editor.js
@@ -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
diff --git a/resources/assets/js/vues/components/autosuggest.js b/resources/assets/js/vues/components/autosuggest.js
new file mode 100644
index 000000000..4d6b97e55
--- /dev/null
+++ b/resources/assets/js/vues/components/autosuggest.js
@@ -0,0 +1,130 @@
+
+const template = `
+    
+ +
    +
  • {{suggestion}}
  • +
+
+ +`; + +function data() { + return { + suggestions: [], + showSuggestions: false, + active: 0, + }; +} + +const ajaxCache = {}; + +const props = ['url', 'type', 'value', 'placeholder', 'name']; + +function getNameInputVal(valInput) { + let parentRow = valInput.parentNode.parentNode; + let nameInput = parentRow.querySelector('[autosuggest-type="name"]'); + return (nameInput === null) ? '' : nameInput.value; +} + +const methods = { + + inputUpdate(inputValue) { + this.$emit('input', inputValue); + let params = {}; + + if (this.type === 'value') { + let nameVal = getNameInputVal(this.$el); + if (nameVal !== "") params.name = nameVal; + } + + this.getSuggestions(inputValue.slice(0, 3), params).then(suggestions => { + if (inputValue.length === 0) { + this.displaySuggestions(suggestions.slice(0, 6)); + return; + } + // Filter to suggestions containing searched term + suggestions = suggestions.filter(item => { + return item.toLowerCase().indexOf(inputValue.toLowerCase()) !== -1; + }).slice(0, 4); + this.displaySuggestions(suggestions); + }); + }, + + inputBlur() { + setTimeout(() => { + this.$emit('blur'); + this.showSuggestions = false; + }, 100); + }, + + inputKeydown(event) { + if (event.keyCode === 13) event.preventDefault(); + if (!this.showSuggestions) return; + + // Down arrow + if (event.keyCode === 40) { + this.active = (this.active === this.suggestions.length - 1) ? 0 : this.active+1; + } + // Up Arrow + else if (event.keyCode === 38) { + this.active = (this.active === 0) ? this.suggestions.length - 1 : this.active-1; + } + // Enter or tab keys + else if ((event.keyCode === 13 || event.keyCode === 9) && !event.shiftKey) { + this.selectSuggestion(this.suggestions[this.active]); + } + // Escape key + else if (event.keyCode === 27) { + this.showSuggestions = false; + } + }, + + displaySuggestions(suggestions) { + if (suggestions.length === 0) { + this.suggestions = []; + this.showSuggestions = false; + return; + } + + this.suggestions = suggestions; + this.showSuggestions = true; + this.active = 0; + }, + + selectSuggestion(suggestion) { + this.$refs.input.value = suggestion; + this.$refs.input.focus(); + this.$emit('input', suggestion); + this.showSuggestions = false; + }, + + /** + * Get suggestions from BookStack. Store and use local cache if already searched. + * @param {String} input + * @param {Object} params + */ + getSuggestions(input, params) { + params.search = input; + let cacheKey = `${this.url}:${JSON.stringify(params)}`; + + if (typeof ajaxCache[cacheKey] !== "undefined") return Promise.resolve(ajaxCache[cacheKey]); + + return this.$http.get(this.url, {params}).then(resp => { + ajaxCache[cacheKey] = resp.data; + return resp.data; + }); + } + +}; + +const computed = []; + +module.exports = {template, data, props, methods, computed}; \ No newline at end of file diff --git a/resources/assets/js/vues/components/dropzone.js b/resources/assets/js/vues/components/dropzone.js new file mode 100644 index 000000000..0f31bd579 --- /dev/null +++ b/resources/assets/js/vues/components/dropzone.js @@ -0,0 +1,60 @@ +const DropZone = require("dropzone"); + +const template = ` +
+
{{placeholder}}
+
+`; + +const props = ['placeholder', 'uploadUrl', 'uploadedTo']; + +// TODO - Remove jQuery usage +function mounted() { + let container = this.$el; + let _this = this; + new DropZone(container, { + url: function() { + return _this.uploadUrl; + }, + init: function () { + let dz = this; + + dz.on('sending', function (file, xhr, data) { + let token = window.document.querySelector('meta[name=token]').getAttribute('content'); + data.append('_token', token); + let uploadedTo = typeof _this.uploadedTo === 'undefined' ? 0 : _this.uploadedTo; + data.append('uploaded_to', uploadedTo); + }); + + dz.on('success', function (file, data) { + _this.$emit('success', {file, data}); + $(file.previewElement).fadeOut(400, function () { + dz.removeFile(file); + }); + }); + + dz.on('error', function (file, errorMessage, xhr) { + _this.$emit('error', {file, errorMessage, xhr}); + console.log(errorMessage); + console.log(xhr); + function setMessage(message) { + $(file.previewElement).find('[data-dz-errormessage]').text(message); + } + + if (xhr.status === 413) setMessage(trans('errors.server_upload_limit')); + if (errorMessage.file) setMessage(errorMessage.file[0]); + }); + } + }); +} + +function data() { + return {} +} + +module.exports = { + template, + props, + mounted, + data, +}; \ No newline at end of file diff --git a/resources/assets/js/vues/image-manager.js b/resources/assets/js/vues/image-manager.js new file mode 100644 index 000000000..9e3fa013e --- /dev/null +++ b/resources/assets/js/vues/image-manager.js @@ -0,0 +1,182 @@ +const dropzone = require('./components/dropzone'); + +let page = 0; +let previousClickTime = 0; +let previousClickImage = 0; +let dataLoaded = false; +let callback = false; +let baseUrl = ''; + +let preSearchImages = []; +let preSearchHasMore = false; + +const data = { + images: [], + + imageType: false, + uploadedTo: false, + + selectedImage: false, + dependantPages: false, + showing: false, + view: 'all', + hasMore: false, + searching: false, + searchTerm: '', + + imageUpdateSuccess: false, + imageDeleteSuccess: false, +}; + +const methods = { + + show(providedCallback) { + callback = providedCallback; + this.showing = true; + this.$el.children[0].components.overlay.show(); + + // Get initial images if they have not yet been loaded in. + if (dataLoaded) return; + this.fetchData(); + dataLoaded = true; + }, + + hide() { + this.showing = false; + this.$el.children[0].components.overlay.hide(); + }, + + fetchData() { + let url = baseUrl + page; + let query = {}; + if (this.uploadedTo !== false) query.page_id = this.uploadedTo; + if (this.searching) query.term = this.searchTerm; + + this.$http.get(url, {params: query}).then(response => { + this.images = this.images.concat(response.data.images); + this.hasMore = response.data.hasMore; + page++; + }); + }, + + setView(viewName) { + this.cancelSearch(); + this.images = []; + this.hasMore = false; + page = 0; + this.view = viewName; + baseUrl = window.baseUrl(`/images/${this.imageType}/${viewName}/`); + this.fetchData(); + }, + + searchImages() { + if (this.searchTerm === '') return this.cancelSearch(); + + // Cache current settings for later + if (!this.searching) { + preSearchImages = this.images; + preSearchHasMore = this.hasMore; + } + + this.searching = true; + this.images = []; + this.hasMore = false; + page = 0; + baseUrl = window.baseUrl(`/images/${this.imageType}/search/`); + this.fetchData(); + }, + + cancelSearch() { + this.searching = false; + this.searchTerm = ''; + this.images = preSearchImages; + this.hasMore = preSearchHasMore; + }, + + imageSelect(image) { + let dblClickTime = 300; + let currentTime = Date.now(); + let timeDiff = currentTime - previousClickTime; + let isDblClick = timeDiff < dblClickTime && image.id === previousClickImage; + + if (isDblClick) { + this.callbackAndHide(image); + } else { + this.selectedImage = image; + this.dependantPages = false; + } + + previousClickTime = currentTime; + previousClickImage = image.id; + }, + + callbackAndHide(imageResult) { + if (callback) callback(imageResult); + this.hide(); + }, + + saveImageDetails() { + let url = window.baseUrl(`/images/update/${this.selectedImage.id}`); + this.$http.put(url, this.selectedImage).then(response => { + this.$events.emit('success', trans('components.image_update_success')); + }).catch(error => { + if (error.response.status === 422) { + let errors = error.response.data; + let message = ''; + Object.keys(errors).forEach((key) => { + message += errors[key].join('\n'); + }); + this.$events.emit('error', message); + } else if (error.response.status === 403) { + this.$events.emit('error', error.response.data.error); + } + }); + }, + + deleteImage() { + let force = this.dependantPages !== false; + let url = window.baseUrl('/images/' + this.selectedImage.id); + if (force) url += '?force=true'; + this.$http.delete(url).then(response => { + this.images.splice(this.images.indexOf(this.selectedImage), 1); + this.selectedImage = false; + this.$events.emit('success', trans('components.image_delete_success')); + }).catch(error=> { + if (error.response.status === 400) { + this.dependantPages = error.response.data; + } else if (error.response.status === 403) { + this.$events.emit('error', error.response.data.error); + } + }); + }, + + getDate(stringDate) { + return new Date(stringDate); + }, + + uploadSuccess(event) { + this.images.unshift(event.data); + this.$events.emit('success', trans('components.image_upload_success')); + }, +}; + +const computed = { + uploadUrl() { + return window.baseUrl(`/images/${this.imageType}/upload`); + } +}; + +function mounted() { + window.ImageManager = this; + this.imageType = this.$el.getAttribute('image-type'); + this.uploadedTo = this.$el.getAttribute('uploaded-to'); + baseUrl = window.baseUrl('/images/' + this.imageType + '/all/') +} + +module.exports = { + mounted, + methods, + data, + computed, + components: {dropzone}, +}; \ No newline at end of file diff --git a/resources/assets/js/vues/search.js b/resources/assets/js/vues/search.js index 515ca3bc9..8cb790d24 100644 --- a/resources/assets/js/vues/search.js +++ b/resources/assets/js/vues/search.js @@ -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 +}; diff --git a/resources/assets/js/vues/tag-manager.js b/resources/assets/js/vues/tag-manager.js new file mode 100644 index 000000000..d97ceb96b --- /dev/null +++ b/resources/assets/js/vues/tag-manager.js @@ -0,0 +1,68 @@ +const draggable = require('vuedraggable'); +const autosuggest = require('./components/autosuggest'); + +let data = { + pageId: false, + tags: [], +}; + +const components = {draggable, autosuggest}; +const directives = {}; + +let computed = {}; + +let methods = { + + addEmptyTag() { + this.tags.push({name: '', value: '', key: Math.random().toString(36).substring(7)}); + }, + + /** + * When an tag changes check if another empty editable field needs to be added onto the end. + * @param tag + */ + tagChange(tag) { + let tagPos = this.tags.indexOf(tag); + if (tagPos === this.tags.length-1 && (tag.name !== '' || tag.value !== '')) this.addEmptyTag(); + }, + + /** + * When an tag field loses focus check the tag to see if its + * empty and therefore could be removed from the list. + * @param tag + */ + tagBlur(tag) { + let isLast = (this.tags.indexOf(tag) === this.tags.length-1); + if (tag.name !== '' || tag.value !== '' || isLast) return; + let cPos = this.tags.indexOf(tag); + this.tags.splice(cPos, 1); + }, + + removeTag(tag) { + let tagPos = this.tags.indexOf(tag); + if (tagPos === -1) return; + this.tags.splice(tagPos, 1); + }, + + getTagFieldName(index, key) { + return `tags[${index}][${key}]`; + }, +}; + +function mounted() { + this.pageId = Number(this.$el.getAttribute('page-id')); + + let url = window.baseUrl(`/ajax/tags/get/page/${this.pageId}`); + this.$http.get(url).then(response => { + let tags = response.data; + for (let i = 0, len = tags.length; i < len; i++) { + tags[i].key = Math.random().toString(36).substring(7); + } + this.tags = tags; + this.addEmptyTag(); + }); +} + +module.exports = { + data, computed, methods, mounted, components, directives +}; \ No newline at end of file diff --git a/resources/assets/js/vues/vues.js b/resources/assets/js/vues/vues.js index 8cc1dd656..a3f6ec8e5 100644 --- a/resources/assets/js/vues/vues.js +++ b/resources/assets/js/vues/vues.js @@ -7,12 +7,17 @@ function exists(id) { let vueMapping = { 'search-system': require('./search'), 'entity-dashboard': require('./entity-search'), + 'code-editor': require('./code-editor'), + 'image-manager': require('./image-manager'), + 'tag-manager': require('./tag-manager'), }; +window.vues = {}; + Object.keys(vueMapping).forEach(id => { if (exists(id)) { let config = vueMapping[id]; config.el = '#' + id; - new Vue(config); + window.vues[id] = new Vue(config); } }); \ No newline at end of file diff --git a/resources/assets/sass/_animations.scss b/resources/assets/sass/_animations.scss index 467399a66..015a23ab1 100644 --- a/resources/assets/sass/_animations.scss +++ b/resources/assets/sass/_animations.scss @@ -36,41 +36,12 @@ } } -.anim.notification { - transform: translate3d(580px, 0, 0); - animation-name: notification; - animation-duration: 3s; - animation-timing-function: ease-in-out; - animation-fill-mode: forwards; - &.stopped { - animation-name: notificationStopped; - } -} - -@keyframes notification { - 0% { - transform: translate3d(580px, 0, 0); - } - 10% { - transform: translate3d(0, 0, 0); - } - 90% { - transform: translate3d(0, 0, 0); - } - 100% { - transform: translate3d(580px, 0, 0); - } -} -@keyframes notificationStopped { - 0% { - transform: translate3d(580px, 0, 0); - } - 10% { - transform: translate3d(0, 0, 0); - } - 100% { - transform: translate3d(0, 0, 0); - } +.anim.menuIn { + transform-origin: 100% 0%; + animation-name: menuIn; + animation-duration: 120ms; + animation-delay: 0s; + animation-timing-function: cubic-bezier(.62, .28, .23, .99); } @keyframes menuIn { @@ -85,14 +56,6 @@ } } -.anim.menuIn { - transform-origin: 100% 0%; - animation-name: menuIn; - animation-duration: 120ms; - animation-delay: 0s; - animation-timing-function: cubic-bezier(.62, .28, .23, .99); -} - @keyframes loadingBob { 0% { transform: translate3d(0, 0, 0); diff --git a/resources/assets/sass/_codemirror.scss b/resources/assets/sass/_codemirror.scss index 9f9e38f55..bd85218a5 100644 --- a/resources/assets/sass/_codemirror.scss +++ b/resources/assets/sass/_codemirror.scss @@ -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; diff --git a/resources/assets/sass/_components.scss b/resources/assets/sass/_components.scss index 5328057d9..8092caa07 100644 --- a/resources/assets/sass/_components.scss +++ b/resources/assets/sass/_components.scss @@ -1,4 +1,65 @@ -.overlay { +// System wide notifications +[notification] { + position: fixed; + top: 0; + right: 0; + margin: $-xl*2 $-xl; + padding: $-l $-xl; + background-color: #EEE; + border-radius: 3px; + box-shadow: $bs-med; + z-index: 999999; + display: block; + cursor: pointer; + max-width: 480px; + transition: transform ease-in-out 360ms; + transform: translate3d(580px, 0, 0); + i, span { + display: table-cell; + } + i { + font-size: 2em; + padding-right: $-l; + } + span { + vertical-align: middle; + } + &.pos { + background-color: $positive; + color: #EEE; + } + &.neg { + background-color: $negative; + color: #EEE; + } + &.warning { + background-color: $secondary; + color: #EEE; + } + &.showing { + transform: translate3d(0, 0, 0); + } +} + +[chapter-toggle] { + cursor: pointer; + margin: 0; + transition: all ease-in-out 180ms; + user-select: none; + i.zmdi-caret-right { + transition: all ease-in-out 180ms; + transform: rotate(0deg); + transform-origin: 25% 50%; + } + &.open { + //margin-bottom: 0; + } + &.open i.zmdi-caret-right { + transform: rotate(90deg); + } +} + +[overlay] { background-color: rgba(0, 0, 0, 0.333); position: fixed; z-index: 95536; @@ -466,4 +527,17 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { .image-picker .none { display: none; +} + +#code-editor .CodeMirror { + height: 400px; +} + +#code-editor .lang-options { + max-width: 400px; + margin-bottom: $-s; + a { + margin-right: $-xs; + text-decoration: underline; + } } \ No newline at end of file diff --git a/resources/assets/sass/_forms.scss b/resources/assets/sass/_forms.scss index 1fc812896..866316fc5 100644 --- a/resources/assets/sass/_forms.scss +++ b/resources/assets/sass/_forms.scss @@ -32,7 +32,7 @@ #markdown-editor { position: relative; z-index: 5; - textarea { + #markdown-editor-input { font-family: 'Roboto Mono', monospace; font-style: normal; font-weight: 400; @@ -265,7 +265,7 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] { } } -input.outline { +.outline > input { border: 0; border-bottom: 2px solid #DDD; border-radius: 0; diff --git a/resources/assets/sass/_header.scss b/resources/assets/sass/_header.scss index 12bd17076..ae8dd3ff5 100644 --- a/resources/assets/sass/_header.scss +++ b/resources/assets/sass/_header.scss @@ -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 { diff --git a/resources/assets/sass/_lists.scss b/resources/assets/sass/_lists.scss index 051268926..d08ccc9bb 100644 --- a/resources/assets/sass/_lists.scss +++ b/resources/assets/sass/_lists.scss @@ -9,7 +9,6 @@ .inset-list { display: none; overflow: hidden; - margin-bottom: $-l; } h5 { display: block; @@ -22,6 +21,9 @@ border-left-color: $color-page-draft; } } + .entity-list-item { + margin-bottom: $-m; + } hr { margin-top: 0; } @@ -51,23 +53,6 @@ margin-right: $-s; } } -.chapter-toggle { - cursor: pointer; - margin: 0 0 $-l 0; - transition: all ease-in-out 180ms; - user-select: none; - i.zmdi-caret-right { - transition: all ease-in-out 180ms; - transform: rotate(0deg); - transform-origin: 25% 50%; - } - &.open { - margin-bottom: 0; - } - &.open i.zmdi-caret-right { - transform: rotate(90deg); - } -} .sidebar-page-nav { $nav-indent: $-s; @@ -171,7 +156,7 @@ background-color: rgba($color-chapter, 0.12); } } - .chapter-toggle { + [chapter-toggle] { padding-left: $-s; } .list-item-chapter { @@ -336,8 +321,10 @@ ul.pagination { h4, a { line-height: 1.2; } - p { + .entity-item-snippet { display: none; + } + p { font-size: $fs-m * 0.8; padding-top: $-xs; margin: 0; diff --git a/resources/assets/sass/_pages.scss b/resources/assets/sass/_pages.scss index b06892c1d..65fdfbc4b 100755 --- a/resources/assets/sass/_pages.scss +++ b/resources/assets/sass/_pages.scss @@ -226,7 +226,7 @@ width: 100%; min-width: 50px; } - .tags td { + .tags td, .tag-table > div > div > div { padding-right: $-s; padding-top: $-s; position: relative; diff --git a/resources/assets/sass/_tables.scss b/resources/assets/sass/_tables.scss index 21553b839..ea517fee3 100644 --- a/resources/assets/sass/_tables.scss +++ b/resources/assets/sass/_tables.scss @@ -67,4 +67,17 @@ table.file-table { .ui-sortable-helper { display: table; } +} + +.fake-table { + display: table; + > div { + display: table-row-group; + } + > div > div { + display: table-row; + } + > div > div > div { + display: table-cell; + } } \ No newline at end of file diff --git a/resources/assets/sass/_text.scss b/resources/assets/sass/_text.scss index 4eaa492e7..2ef4bd16d 100644 --- a/resources/assets/sass/_text.scss +++ b/resources/assets/sass/_text.scss @@ -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 diff --git a/resources/assets/sass/styles.scss b/resources/assets/sass/styles.scss index 3b279b8bd..e40430bd8 100644 --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@ -66,44 +66,6 @@ body.dragging, body.dragging * { } } -// System wide notifications -.notification { - position: fixed; - top: 0; - right: 0; - margin: $-xl*2 $-xl; - padding: $-l $-xl; - background-color: #EEE; - border-radius: 3px; - box-shadow: $bs-med; - z-index: 999999; - display: block; - cursor: pointer; - max-width: 480px; - i, span { - display: table-cell; - } - i { - font-size: 2em; - padding-right: $-l; - } - span { - vertical-align: middle; - } - &.pos { - background-color: $positive; - color: #EEE; - } - &.neg { - background-color: $negative; - color: #EEE; - } - &.warning { - background-color: $secondary; - color: #EEE; - } -} - // Loading icon $loadingSize: 10px; .loading-container { @@ -151,7 +113,7 @@ $loadingSize: 10px; // Back to top link $btt-size: 40px; -#back-to-top { +[back-to-top] { background-color: $primary; position: fixed; bottom: $-m; diff --git a/resources/lang/en/components.php b/resources/lang/en/components.php index b9108702a..334502d05 100644 --- a/resources/lang/en/components.php +++ b/resources/lang/en/components.php @@ -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 diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index 31163e87e..3eec7737f 100644 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -121,6 +121,8 @@ return [ 'nl' => 'Nederlands', 'pt_BR' => 'Português do Brasil', 'sk' => 'Slovensky', + 'ja' => '日本語', + 'pl' => 'Polski', ] /////////////////////////////////// ]; diff --git a/resources/lang/fr/auth.php b/resources/lang/fr/auth.php index 41d051c5f..015bfdff0 100644 --- a/resources/lang/fr/auth.php +++ b/resources/lang/fr/auth.php @@ -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', diff --git a/resources/lang/fr/common.php b/resources/lang/fr/common.php index 5eb4b8fa8..7a8ec55b6 100644 --- a/resources/lang/fr/common.php +++ b/resources/lang/fr/common.php @@ -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', ]; diff --git a/resources/lang/fr/entities.php b/resources/lang/fr/entities.php index c618bab08..0d89993e9 100644 --- a/resources/lang/fr/entities.php +++ b/resources/lang/fr/entities.php @@ -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 diff --git a/resources/lang/fr/errors.php b/resources/lang/fr/errors.php index 402eeb405..9e20147b6 100644 --- a/resources/lang/fr/errors.php +++ b/resources/lang/fr/errors.php @@ -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 diff --git a/resources/lang/fr/passwords.php b/resources/lang/fr/passwords.php index 7be81da23..484b4b20c 100644 --- a/resources/lang/fr/passwords.php +++ b/resources/lang/fr/passwords.php @@ -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é !', ]; diff --git a/resources/lang/fr/settings.php b/resources/lang/fr/settings.php index 8a3756527..92e623795 100644 --- a/resources/lang/fr/settings.php +++ b/resources/lang/fr/settings.php @@ -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 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 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.
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.
Leave empty to reset to the default color.', + 'app_primary_color_desc' => 'Cela devrait être une valeur hexadécimale.
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.
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 index 000000000..b907e0c63 --- /dev/null +++ b/resources/lang/ja/activities.php @@ -0,0 +1,40 @@ + 'がページを作成:', + '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 index 000000000..4d5aee8b3 --- /dev/null +++ b/resources/lang/ja/auth.php @@ -0,0 +1,76 @@ + 'この資格情報は登録されていません。', + '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 index 000000000..383294880 --- /dev/null +++ b/resources/lang/ja/common.php @@ -0,0 +1,59 @@ + 'キャンセル', + '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 index 000000000..89ed33ef3 --- /dev/null +++ b/resources/lang/ja/components.php @@ -0,0 +1,24 @@ + '画像を選択', + '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 index 000000000..8d215516d --- /dev/null +++ b/resources/lang/ja/entities.php @@ -0,0 +1,236 @@ + '最近作成', + '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 index 000000000..5905237fd --- /dev/null +++ b/resources/lang/ja/errors.php @@ -0,0 +1,70 @@ + 'リクエストされたページへの権限がありません。', + '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 index 000000000..1ebcef722 --- /dev/null +++ b/resources/lang/ja/pagination.php @@ -0,0 +1,19 @@ + '« 前', + 'next' => '次 »', + +]; diff --git a/resources/lang/ja/passwords.php b/resources/lang/ja/passwords.php new file mode 100644 index 000000000..17c82e299 --- /dev/null +++ b/resources/lang/ja/passwords.php @@ -0,0 +1,22 @@ + 'パスワードは6文字以上である必要があります。', + 'user' => "このEメールアドレスに一致するユーザが見つかりませんでした。", + 'token' => 'このパスワードリセットトークンは無効です。', + 'sent' => 'パスワードリセットリンクを送信しました。', + 'reset' => 'パスワードはリセットされました。', + +]; diff --git a/resources/lang/ja/settings.php b/resources/lang/ja/settings.php new file mode 100644 index 000000000..b4cf57aeb --- /dev/null +++ b/resources/lang/ja/settings.php @@ -0,0 +1,112 @@ + '設定', + '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' => 'スタイルシートやアナリティクスコード追加したい場合、ここを編集します。これはの最下部に挿入されます。', + '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メール認証が必須になります。
登録後、ユーザは自由に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 index 000000000..e0fa3cb2c --- /dev/null +++ b/resources/lang/ja/validation.php @@ -0,0 +1,108 @@ + ':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 index 000000000..5ef5acab0 --- /dev/null +++ b/resources/lang/pl/activities.php @@ -0,0 +1,40 @@ + '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 index 000000000..740e067ca --- /dev/null +++ b/resources/lang/pl/auth.php @@ -0,0 +1,76 @@ + '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 index 000000000..1c8963653 --- /dev/null +++ b/resources/lang/pl/common.php @@ -0,0 +1,59 @@ + '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 index 000000000..c1dbcd44b --- /dev/null +++ b/resources/lang/pl/components.php @@ -0,0 +1,32 @@ + '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 index 000000000..30e853bce --- /dev/null +++ b/resources/lang/pl/entities.php @@ -0,0 +1,237 @@ + '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 index 000000000..633bf7a2d --- /dev/null +++ b/resources/lang/pl/errors.php @@ -0,0 +1,70 @@ + '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 index 000000000..564694190 --- /dev/null +++ b/resources/lang/pl/pagination.php @@ -0,0 +1,19 @@ + '« Poprzednia', + 'next' => 'Następna »', + +]; diff --git a/resources/lang/pl/passwords.php b/resources/lang/pl/passwords.php new file mode 100644 index 000000000..a9e669f4d --- /dev/null +++ b/resources/lang/pl/passwords.php @@ -0,0 +1,22 @@ + '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 index 000000000..381e5517a --- /dev/null +++ b/resources/lang/pl/settings.php @@ -0,0 +1,111 @@ + '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 ', + 'app_custom_html_desc' => 'Zawartość dodana tutaj zostanie dołączona do sekcji 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.
Większe obrazki będą skalowane w dół.', + 'app_primary_color' => 'Podstawowy kolor aplikacji', + 'app_primary_color_desc' => 'To powinna być wartość HEX.
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.
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 index 000000000..6a7c13e80 --- /dev/null +++ b/resources/lang/pl/validation.php @@ -0,0 +1,108 @@ + ':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' => [], + +]; diff --git a/resources/views/base.blade.php b/resources/views/base.blade.php index 95a9d72b0..b1cacf08c 100644 --- a/resources/views/base.blade.php +++ b/resources/views/base.blade.php @@ -77,7 +77,7 @@ @yield('content') -
+
{{ trans('common.back_to_top') }}
diff --git a/resources/views/books/list-item.blade.php b/resources/views/books/list-item.blade.php index 605841f7f..92d0f9e2d 100644 --- a/resources/views/books/list-item.blade.php +++ b/resources/views/books/list-item.blade.php @@ -1,8 +1,10 @@

{{$book->name}}

- @if(isset($book->searchSnippet)) -

{!! $book->searchSnippet !!}

- @else -

{{ $book->getExcerpt() }}

- @endif +
+ @if(isset($book->searchSnippet)) +

{!! $book->searchSnippet !!}

+ @else +

{{ $book->getExcerpt() }}

+ @endif +
\ No newline at end of file diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index adfec4525..9882f09a2 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -5,10 +5,10 @@
-
+
@include('books._breadcrumbs', ['book' => $book])
-
+
{{ trans('entities.export') }}
@@ -50,13 +50,13 @@
-
+

{{$book->name}}

-

{{$book->description}}

+

{!! nl2br(e($book->description)) !!}


@@ -72,9 +72,15 @@ @else

{{ trans('entities.books_empty_contents') }}

+ @if(userCan('page-create', $book)) {{ trans('entities.books_empty_create_page') }} + @endif + @if(userCan('page-create', $book) && userCan('chapter-create', $book))   -{{ trans('entities.books_empty_or') }}-    + @endif + @if(userCan('chapter-create', $book)) {{ trans('entities.books_empty_add_chapter') }} + @endif


@endif @@ -106,13 +112,13 @@ @endif - +

{{ trans('entities.recent_activity') }}

@include('partials/activity-list', ['activity' => Activity::entityActivity($book, 20, 0)]) @@ -121,4 +127,4 @@
-@stop \ No newline at end of file +@stop diff --git a/resources/views/chapters/list-item.blade.php b/resources/views/chapters/list-item.blade.php index 8487a63a3..1572f0d9b 100644 --- a/resources/views/chapters/list-item.blade.php +++ b/resources/views/chapters/list-item.blade.php @@ -2,7 +2,7 @@

@if (isset($showPath) && $showPath) - {{ $chapter->book->name }} + {{ $chapter->book->getShortName() }}   »   @endif @@ -10,14 +10,18 @@ {{ $chapter->name }}

- @if(isset($chapter->searchSnippet)) -

{!! $chapter->searchSnippet !!}

- @else -

{{ $chapter->getExcerpt() }}

- @endif + +
+ @if(isset($chapter->searchSnippet)) +

{!! $chapter->searchSnippet !!}

+ @else +

{{ $chapter->getExcerpt() }}

+ @endif +
+ @if(!isset($hidePages) && count($chapter->pages) > 0) -

{{ trans('entities.x_pages', ['count' => $chapter->pages->count()]) }}

+

{{ trans('entities.x_pages', ['count' => $chapter->pages->count()]) }}

@foreach($chapter->pages as $page)
{{$page->name}}
diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index d4126cbcc..9a3195555 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -5,10 +5,10 @@
-
+
@include('chapters._breadcrumbs', ['chapter' => $chapter])
-
+
{{ trans('entities.export') }}
@@ -47,12 +47,12 @@
-
+

{{ $chapter->name }}

-

{{ $chapter->description }}

+

{!! nl2br(e($chapter->description)) !!}

@if(count($pages) > 0)
@@ -116,7 +116,7 @@ @endif