mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-09-06 01:22:23 +00:00
Merge branch 'master' into fix-1186
This commit is contained in:
commit
f467185e86
@ -75,6 +75,12 @@ CACHE_PREFIX=bookstack
|
|||||||
# For multiple servers separate with a comma
|
# For multiple servers separate with a comma
|
||||||
MEMCACHED_SERVERS=127.0.0.1:11211:100
|
MEMCACHED_SERVERS=127.0.0.1:11211:100
|
||||||
|
|
||||||
|
# Redis server configuration
|
||||||
|
# This follows the following format: HOST:PORT:DATABASE
|
||||||
|
# or, if using a password: HOST:PORT:DATABASE:PASSWORD
|
||||||
|
# For multiple servers separate with a comma. These will be clustered.
|
||||||
|
REDIS_SERVERS=127.0.0.1:6379:0
|
||||||
|
|
||||||
# Queue driver to use
|
# Queue driver to use
|
||||||
# Queue not really currently used but may be configurable in the future.
|
# Queue not really currently used but may be configurable in the future.
|
||||||
# Would advise not to change this for now.
|
# Would advise not to change this for now.
|
||||||
|
@ -176,8 +176,8 @@ class LdapService
|
|||||||
* the LDAP_OPT_X_TLS_REQUIRE_CERT option. It can only be set globally and not
|
* the LDAP_OPT_X_TLS_REQUIRE_CERT option. It can only be set globally and not
|
||||||
* per handle.
|
* per handle.
|
||||||
*/
|
*/
|
||||||
if($this->config['tls_insecure']) {
|
if ($this->config['tls_insecure']) {
|
||||||
$this->ldap->setOption(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
|
$this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
$ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort);
|
$ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort);
|
||||||
|
@ -190,10 +190,10 @@ class PermissionService
|
|||||||
{
|
{
|
||||||
return $this->entityProvider->book->newQuery()
|
return $this->entityProvider->book->newQuery()
|
||||||
->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
|
->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
|
||||||
$query->select(['id', 'restricted', 'created_by', 'book_id']);
|
$query->select(['id', 'restricted', 'created_by', 'book_id']);
|
||||||
}, 'pages' => function ($query) {
|
}, 'pages' => function ($query) {
|
||||||
$query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
|
$query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -612,13 +612,13 @@ class PermissionService
|
|||||||
$entities = $this->entityProvider;
|
$entities = $this->entityProvider;
|
||||||
$pageSelect = $this->db->table('pages')->selectRaw($entities->page->entityRawQuery($fetchPageContent))
|
$pageSelect = $this->db->table('pages')->selectRaw($entities->page->entityRawQuery($fetchPageContent))
|
||||||
->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
|
->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
|
||||||
$query->where('draft', '=', 0);
|
$query->where('draft', '=', 0);
|
||||||
if (!$filterDrafts) {
|
if (!$filterDrafts) {
|
||||||
$query->orWhere(function ($query) {
|
$query->orWhere(function ($query) {
|
||||||
$query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
|
$query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$chapterSelect = $this->db->table('chapters')->selectRaw($entities->chapter->entityRawQuery())->where('book_id', '=', $book_id);
|
$chapterSelect = $this->db->table('chapters')->selectRaw($entities->chapter->entityRawQuery())->where('book_id', '=', $book_id);
|
||||||
$query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
|
$query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
|
||||||
->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
|
->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
|
||||||
|
@ -84,6 +84,4 @@ class EntityProvider
|
|||||||
$type = strtolower($type);
|
$type = strtolower($type);
|
||||||
return $this->all()[$type];
|
return $this->all()[$type];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -505,4 +505,4 @@ class PageRepo extends EntityRepo
|
|||||||
|
|
||||||
return $this->publishPageDraft($copyPage, $pageData);
|
return $this->publishPageDraft($copyPage, $pageData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,6 @@
|
|||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
class HttpFetchException extends Exception {}
|
class HttpFetchException extends Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
<?php namespace BookStack\Exceptions;
|
<?php namespace BookStack\Exceptions;
|
||||||
|
|
||||||
class UserUpdateException extends NotifyException {}
|
class UserUpdateException extends NotifyException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
@ -51,6 +51,7 @@ class Localization
|
|||||||
public function handle($request, Closure $next)
|
public function handle($request, Closure $next)
|
||||||
{
|
{
|
||||||
$defaultLang = config('app.locale');
|
$defaultLang = config('app.locale');
|
||||||
|
config()->set('app.default_locale', $defaultLang);
|
||||||
|
|
||||||
if (user()->isDefault() && config('app.auto_detect_locale')) {
|
if (user()->isDefault() && config('app.auto_detect_locale')) {
|
||||||
$locale = $this->autoDetectLocale($request, $defaultLang);
|
$locale = $this->autoDetectLocale($request, $defaultLang);
|
||||||
@ -63,8 +64,6 @@ class Localization
|
|||||||
config()->set('app.rtl', true);
|
config()->set('app.rtl', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app()->setLocale($locale);
|
app()->setLocale($locale);
|
||||||
Carbon::setLocale($locale);
|
Carbon::setLocale($locale);
|
||||||
$this->setSystemDateLocale($locale);
|
$this->setSystemDateLocale($locale);
|
||||||
|
@ -31,5 +31,4 @@ class MailNotification extends Notification implements ShouldQueue
|
|||||||
'text' => 'vendor.notifications.email-plain'
|
'text' => 'vendor.notifications.email-plain'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?php namespace BookStack\Notifications;
|
<?php namespace BookStack\Notifications;
|
||||||
|
|
||||||
|
|
||||||
class ResetPassword extends MailNotification
|
class ResetPassword extends MailNotification
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?php namespace BookStack\Providers;
|
<?php namespace BookStack\Providers;
|
||||||
|
|
||||||
|
|
||||||
use BookStack\Translation\Translator;
|
use BookStack\Translation\Translator;
|
||||||
|
|
||||||
class TranslationServiceProvider extends \Illuminate\Translation\TranslationServiceProvider
|
class TranslationServiceProvider extends \Illuminate\Translation\TranslationServiceProvider
|
||||||
@ -29,4 +28,4 @@ class TranslationServiceProvider extends \Illuminate\Translation\TranslationServ
|
|||||||
return $trans;
|
return $trans;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ class SettingService
|
|||||||
if ($default === false) {
|
if ($default === false) {
|
||||||
$default = config('setting-defaults.' . $key, false);
|
$default = config('setting-defaults.' . $key, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($this->localCache[$key])) {
|
if (isset($this->localCache[$key])) {
|
||||||
return $this->localCache[$key];
|
return $this->localCache[$key];
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?php namespace BookStack\Translation;
|
<?php namespace BookStack\Translation;
|
||||||
|
|
||||||
|
|
||||||
class Translator extends \Illuminate\Translation\Translator
|
class Translator extends \Illuminate\Translation\Translator
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -70,5 +69,4 @@ class Translator extends \Illuminate\Translation\Translator
|
|||||||
{
|
{
|
||||||
return $this->baseLocaleMap[$locale] ?? null;
|
return $this->baseLocaleMap[$locale] ?? null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -30,5 +30,4 @@ class HttpFetcher
|
|||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -8,23 +8,39 @@
|
|||||||
* Do not edit this file unless you're happy to maintain any changes yourself.
|
* Do not edit this file unless you're happy to maintain any changes yourself.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// REDIS - Split out configuration into an array
|
// REDIS
|
||||||
|
// Split out configuration into an array
|
||||||
if (env('REDIS_SERVERS', false)) {
|
if (env('REDIS_SERVERS', false)) {
|
||||||
$redisServerKeys = ['host', 'port', 'database'];
|
|
||||||
|
$redisDefaults = ['host' => '127.0.0.1', 'port' => '6379', 'database' => '0', 'password' => null];
|
||||||
$redisServers = explode(',', trim(env('REDIS_SERVERS', '127.0.0.1:6379:0'), ','));
|
$redisServers = explode(',', trim(env('REDIS_SERVERS', '127.0.0.1:6379:0'), ','));
|
||||||
$redisConfig = [
|
$redisConfig = [];
|
||||||
'cluster' => env('REDIS_CLUSTER', false)
|
$cluster = count($redisServers) > 1;
|
||||||
];
|
|
||||||
|
if ($cluster) {
|
||||||
|
$redisConfig['clusters'] = ['default' => []];
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($redisServers as $index => $redisServer) {
|
foreach ($redisServers as $index => $redisServer) {
|
||||||
$redisServerName = ($index === 0) ? 'default' : 'redis-server-' . $index;
|
|
||||||
$redisServerDetails = explode(':', $redisServer);
|
$redisServerDetails = explode(':', $redisServer);
|
||||||
if (count($redisServerDetails) < 2) $redisServerDetails[] = '6379';
|
|
||||||
if (count($redisServerDetails) < 3) $redisServerDetails[] = '0';
|
$serverConfig = [];
|
||||||
$redisConfig[$redisServerName] = array_combine($redisServerKeys, $redisServerDetails);
|
$configIndex = 0;
|
||||||
|
foreach ($redisDefaults as $configKey => $configDefault) {
|
||||||
|
$serverConfig[$configKey] = ($redisServerDetails[$configIndex] ?? $configDefault);
|
||||||
|
$configIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cluster) {
|
||||||
|
$redisConfig['clusters']['default'][] = $serverConfig;
|
||||||
|
} else {
|
||||||
|
$redisConfig['default'] = $serverConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MYSQL - Split out port from host if set
|
// MYSQL
|
||||||
|
// Split out port from host if set
|
||||||
$mysql_host = env('DB_HOST', 'localhost');
|
$mysql_host = env('DB_HOST', 'localhost');
|
||||||
$mysql_host_exploded = explode(':', $mysql_host);
|
$mysql_host_exploded = explode(':', $mysql_host);
|
||||||
$mysql_port = env('DB_PORT', 3306);
|
$mysql_port = env('DB_PORT', 3306);
|
||||||
|
35
readme.md
35
readme.md
@ -9,8 +9,7 @@ A platform for storing and organising information and documentation. General inf
|
|||||||
* [Installation Instructions](https://www.bookstackapp.com/docs/admin/installation)
|
* [Installation Instructions](https://www.bookstackapp.com/docs/admin/installation)
|
||||||
* [Documentation](https://www.bookstackapp.com/docs)
|
* [Documentation](https://www.bookstackapp.com/docs)
|
||||||
* [Demo Instance](https://demo.bookstackapp.com)
|
* [Demo Instance](https://demo.bookstackapp.com)
|
||||||
* *Username: `admin@example.com`*
|
* [Admin Login](https://demo.bookstackapp.com/login?email=admin@example.com&password=password)
|
||||||
* *Password: `password`*
|
|
||||||
* [BookStack Blog](https://www.bookstackapp.com/blog)
|
* [BookStack Blog](https://www.bookstackapp.com/blog)
|
||||||
|
|
||||||
## Project Definition
|
## Project Definition
|
||||||
@ -19,7 +18,30 @@ BookStack is an opinionated wiki system that provides a pleasant and simple out
|
|||||||
|
|
||||||
BookStack is not designed as an extensible platform to be used for purposes that differ to the statement above.
|
BookStack is not designed as an extensible platform to be used for purposes that differ to the statement above.
|
||||||
|
|
||||||
In regards to development philosophy, BookStack has a relaxed, open & positive approach. Put simply, At the end of the day this is free software developed and maintained by people donating their own free time.
|
In regards to development philosophy, BookStack has a relaxed, open & positive approach. At the end of the day this is free software developed and maintained by people donating their own free time.
|
||||||
|
|
||||||
|
## Road Map
|
||||||
|
|
||||||
|
Below is a high-level road map view for BookStack to provide a sense of direction of where the project is going. This can change at any point and does not reflect many features and improvements that will also be included as part of the journey along this road map. For more granular detail of what will be included in upcoming releases you can review the project milestones as defined in the "Release Process" section below.
|
||||||
|
|
||||||
|
- **Design Revamp** *[(In Progress)](https://github.com/BookStackApp/BookStack/pull/1153)*
|
||||||
|
- *A more organised modern design to clean things up, make BookStack more efficient to use and increase mobile usability.*
|
||||||
|
- **Platform REST API**
|
||||||
|
- *A REST API covering, at minimum, control of core content models (Books, Chapters, Pages) for automation and platform extension.*
|
||||||
|
- **Editor Alignment & Review**
|
||||||
|
- *Review the page editors with goal of achieving increased interoperability & feature parity while also considering collaborative editing potential.*
|
||||||
|
- **Permission System Review**
|
||||||
|
- *Improvement in how permissions are applied and a review of the efficiency of the permission & roles system.*
|
||||||
|
- **Installation & Deployment Process Revamp**
|
||||||
|
- *Creation of a streamlined & secure process for users to deploy & update BookStack with reduced development requirements (No git or composer requirement).*
|
||||||
|
|
||||||
|
## Release Versioning & Process
|
||||||
|
|
||||||
|
BookStack releases are each assigned a version number, such as "v0.25.2", in the format `v<phase>.<feature>.<patch>`. A change only in the `patch` number indicates a fairly minor release that mainly contains fixes and therefore is very unlikely to cause breakages upon update. A change in the `feature` number indicates a release which will generally bring new features in addition to fixes and enhancements. These releases have a small chance of introducing breaking changes upon update so it's worth checking for any notes in the [update guide](https://www.bookstackapp.com/docs/admin/updates/). A change in the `phase` indicates a much large change in BookStack that will likely incur breakages requiring manual intervention.
|
||||||
|
|
||||||
|
Each BookStack release will have a [milestone](https://github.com/BookStackApp/BookStack/milestones) created with issues & pull requests assigned to it to define what will be in that release. Milestones are built up then worked through until complete at which point, after some testing and documentation updates, the release will be deployed.
|
||||||
|
|
||||||
|
For feature releases, and some patch releases, the release will be accompanied by a post on the [BookStack blog](https://www.bookstackapp.com/blog/) which will provide additional detail on features, changes & updates otherwise the [GitHub release page](https://github.com/BookStackApp/BookStack/releases) will show a list of changes. You can sign up to be alerted to new BookStack blogs posts (once per week maximum) [at this link](http://eepurl.com/cmmq5j).
|
||||||
|
|
||||||
## Development & Testing
|
## Development & Testing
|
||||||
|
|
||||||
@ -85,15 +107,15 @@ PHP code within BookStack is generally to [PSR-2](http://www.php-fig.org/psr/psr
|
|||||||
|
|
||||||
### Pull Requests
|
### Pull Requests
|
||||||
|
|
||||||
Pull requests are very welcome. If the scope of your pull request is large it may be best to open the pull request early or create an issue for it to discuss how it will fit in to the project and plan out the merge.
|
Pull requests are welcome. Unless a small tweak or language update, It may be best to open the pull request early or create an issue for your intended change to discuss how it will fit in to the project and plan out the merge.
|
||||||
|
|
||||||
Pull requests should be created from the `master` branch and should be merged back into `master` once done. Please do not build from or request a merge into the `release` branch as this is only for publishing releases.
|
Pull requests should be created from the `master` branch since they will be merged back into `master` once done. Please do not build from or request a merge into the `release` branch as this is only for publishing releases.
|
||||||
|
|
||||||
If you are looking to alter CSS or JavaScript content please edit the source files found in `resources/assets`. Any CSS or JS files within `public` are built from these source files and therefore should not be edited directly.
|
If you are looking to alter CSS or JavaScript content please edit the source files found in `resources/assets`. Any CSS or JS files within `public` are built from these source files and therefore should not be edited directly.
|
||||||
|
|
||||||
## Website, Docs & Blog
|
## Website, Docs & Blog
|
||||||
|
|
||||||
The website project docs & Blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo.
|
The website which contains the project docs & Blog can be found in the [BookStackApp/website](https://github.com/BookStackApp/website) repo.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@ -117,7 +139,6 @@ These are the great open-source projects used to help build BookStack:
|
|||||||
* [clipboard.js](https://clipboardjs.com/)
|
* [clipboard.js](https://clipboardjs.com/)
|
||||||
* [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html)
|
* [TinyColorPicker](http://www.dematte.at/tinyColorPicker/index.html)
|
||||||
* [markdown-it](https://github.com/markdown-it/markdown-it) and [markdown-it-task-lists](https://github.com/revin/markdown-it-task-lists)
|
* [markdown-it](https://github.com/markdown-it/markdown-it) and [markdown-it-task-lists](https://github.com/revin/markdown-it-task-lists)
|
||||||
* [Moment.js](http://momentjs.com/)
|
|
||||||
* [BarryVD](https://github.com/barryvdh)
|
* [BarryVD](https://github.com/barryvdh)
|
||||||
* [Debugbar](https://github.com/barryvdh/laravel-debugbar)
|
* [Debugbar](https://github.com/barryvdh/laravel-debugbar)
|
||||||
* [Dompdf](https://github.com/barryvdh/laravel-dompdf)
|
* [Dompdf](https://github.com/barryvdh/laravel-dompdf)
|
||||||
|
@ -8,7 +8,11 @@ class MarkdownEditor {
|
|||||||
|
|
||||||
constructor(elem) {
|
constructor(elem) {
|
||||||
this.elem = elem;
|
this.elem = elem;
|
||||||
this.textDirection = document.getElementById('page-editor').getAttribute('text-direction');
|
|
||||||
|
const pageEditor = document.getElementById('page-editor');
|
||||||
|
this.pageId = pageEditor.getAttribute('page-id');
|
||||||
|
this.textDirection = pageEditor.getAttribute('text-direction');
|
||||||
|
|
||||||
this.markdown = new MarkdownIt({html: true});
|
this.markdown = new MarkdownIt({html: true});
|
||||||
this.markdown.use(mdTasksLists, {label: true});
|
this.markdown.use(mdTasksLists, {label: true});
|
||||||
|
|
||||||
@ -98,7 +102,9 @@ class MarkdownEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
codeMirrorSetup() {
|
codeMirrorSetup() {
|
||||||
let cm = this.cm;
|
const cm = this.cm;
|
||||||
|
const context = this;
|
||||||
|
|
||||||
// Text direction
|
// Text direction
|
||||||
// cm.setOption('direction', this.textDirection);
|
// cm.setOption('direction', this.textDirection);
|
||||||
cm.setOption('direction', 'ltr'); // Will force to remain as ltr for now due to issues when HTML is in editor.
|
cm.setOption('direction', 'ltr'); // Will force to remain as ltr for now due to issues when HTML is in editor.
|
||||||
@ -266,17 +272,18 @@ class MarkdownEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Insert image into markdown
|
// Insert image into markdown
|
||||||
let id = "image-" + Math.random().toString(16).slice(2);
|
const id = "image-" + Math.random().toString(16).slice(2);
|
||||||
let placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
|
const placeholderImage = window.baseUrl(`/loading.gif#upload${id}`);
|
||||||
let selectedText = cm.getSelection();
|
const selectedText = cm.getSelection();
|
||||||
let placeHolderText = `![${selectedText}](${placeholderImage})`;
|
const placeHolderText = `![${selectedText}](${placeholderImage})`;
|
||||||
let cursor = cm.getCursor();
|
const cursor = cm.getCursor();
|
||||||
cm.replaceSelection(placeHolderText);
|
cm.replaceSelection(placeHolderText);
|
||||||
cm.setCursor({line: cursor.line, ch: cursor.ch + selectedText.length + 3});
|
cm.setCursor({line: cursor.line, ch: cursor.ch + selectedText.length + 3});
|
||||||
|
|
||||||
let remoteFilename = "image-" + Date.now() + "." + ext;
|
const remoteFilename = "image-" + Date.now() + "." + ext;
|
||||||
let formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file, remoteFilename);
|
formData.append('file', file, remoteFilename);
|
||||||
|
formData.append('uploaded_to', context.pageId);
|
||||||
|
|
||||||
window.$http.post('/images/gallery/upload', formData).then(resp => {
|
window.$http.post('/images/gallery/upload', formData).then(resp => {
|
||||||
const newContent = `[![${selectedText}](${resp.data.thumbs.display})](${resp.data.url})`;
|
const newContent = `[![${selectedText}](${resp.data.thumbs.display})](${resp.data.url})`;
|
||||||
@ -302,7 +309,7 @@ class MarkdownEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
actionInsertImage() {
|
actionInsertImage() {
|
||||||
let cursorPos = this.cm.getCursor('from');
|
const cursorPos = this.cm.getCursor('from');
|
||||||
window.ImageManager.show(image => {
|
window.ImageManager.show(image => {
|
||||||
let selectedText = this.cm.getSelection();
|
let selectedText = this.cm.getSelection();
|
||||||
let newText = "[![" + (selectedText || image.name) + "](" + image.thumbs.display + ")](" + image.url + ")";
|
let newText = "[![" + (selectedText || image.name) + "](" + image.thumbs.display + ")](" + image.url + ")";
|
||||||
@ -313,7 +320,7 @@ class MarkdownEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
actionShowImageManager() {
|
actionShowImageManager() {
|
||||||
let cursorPos = this.cm.getCursor('from');
|
const cursorPos = this.cm.getCursor('from');
|
||||||
window.ImageManager.show(image => {
|
window.ImageManager.show(image => {
|
||||||
this.insertDrawing(image, cursorPos);
|
this.insertDrawing(image, cursorPos);
|
||||||
}, 'drawio');
|
}, 'drawio');
|
||||||
@ -321,7 +328,7 @@ class MarkdownEditor {
|
|||||||
|
|
||||||
// Show the popup link selector and insert a link when finished
|
// Show the popup link selector and insert a link when finished
|
||||||
actionShowLinkSelector() {
|
actionShowLinkSelector() {
|
||||||
let cursorPos = this.cm.getCursor('from');
|
const cursorPos = this.cm.getCursor('from');
|
||||||
window.EntitySelectorPopup.show(entity => {
|
window.EntitySelectorPopup.show(entity => {
|
||||||
let selectedText = this.cm.getSelection() || entity.name;
|
let selectedText = this.cm.getSelection() || entity.name;
|
||||||
let newText = `[${selectedText}](${entity.link})`;
|
let newText = `[${selectedText}](${entity.link})`;
|
||||||
@ -357,7 +364,7 @@ class MarkdownEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertDrawing(image, originalCursor) {
|
insertDrawing(image, originalCursor) {
|
||||||
let newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`;
|
const newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`;
|
||||||
this.cm.focus();
|
this.cm.focus();
|
||||||
this.cm.replaceSelection(newText);
|
this.cm.replaceSelection(newText);
|
||||||
this.cm.setCursor(originalCursor.line, originalCursor.ch + newText.length);
|
this.cm.setCursor(originalCursor.line, originalCursor.ch + newText.length);
|
||||||
@ -365,9 +372,13 @@ class MarkdownEditor {
|
|||||||
|
|
||||||
// Show draw.io if enabled and handle save.
|
// Show draw.io if enabled and handle save.
|
||||||
actionEditDrawing(imgContainer) {
|
actionEditDrawing(imgContainer) {
|
||||||
if (document.querySelector('[drawio-enabled]').getAttribute('drawio-enabled') !== 'true') return;
|
const drawingDisabled = document.querySelector('[drawio-enabled]').getAttribute('drawio-enabled') !== 'true';
|
||||||
let cursorPos = this.cm.getCursor('from');
|
if (drawingDisabled) {
|
||||||
let drawingId = imgContainer.getAttribute('drawio-diagram');
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursorPos = this.cm.getCursor('from');
|
||||||
|
const drawingId = imgContainer.getAttribute('drawio-diagram');
|
||||||
|
|
||||||
DrawIO.show(() => {
|
DrawIO.show(() => {
|
||||||
return window.$http.get(window.baseUrl(`/images/base64/${drawingId}`)).then(resp => {
|
return window.$http.get(window.baseUrl(`/images/base64/${drawingId}`)).then(resp => {
|
||||||
|
@ -4,22 +4,24 @@ import DrawIO from "../services/drawio";
|
|||||||
/**
|
/**
|
||||||
* Handle pasting images from clipboard.
|
* Handle pasting images from clipboard.
|
||||||
* @param {ClipboardEvent} event
|
* @param {ClipboardEvent} event
|
||||||
|
* @param {WysiwygEditor} wysiwygComponent
|
||||||
* @param editor
|
* @param editor
|
||||||
*/
|
*/
|
||||||
function editorPaste(event, editor) {
|
function editorPaste(event, editor, wysiwygComponent) {
|
||||||
if (!event.clipboardData || !event.clipboardData.items) return;
|
if (!event.clipboardData || !event.clipboardData.items) return;
|
||||||
let items = event.clipboardData.items;
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let clipboardItem of event.clipboardData.items) {
|
||||||
if (items[i].type.indexOf("image") === -1) continue;
|
if (clipboardItem.type.indexOf("image") === -1) continue;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
let id = "image-" + Math.random().toString(16).slice(2);
|
const id = "image-" + Math.random().toString(16).slice(2);
|
||||||
let loadingImage = window.baseUrl('/loading.gif');
|
const loadingImage = window.baseUrl('/loading.gif');
|
||||||
let file = items[i].getAsFile();
|
const file = clipboardItem.getAsFile();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
editor.insertContent(`<p><img src="${loadingImage}" id="${id}"></p>`);
|
editor.insertContent(`<p><img src="${loadingImage}" id="${id}"></p>`);
|
||||||
uploadImageFile(file).then(resp => {
|
|
||||||
|
uploadImageFile(file, wysiwygComponent).then(resp => {
|
||||||
editor.dom.setAttrib(id, 'src', resp.thumbs.display);
|
editor.dom.setAttrib(id, 'src', resp.thumbs.display);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
editor.dom.remove(id);
|
editor.dom.remove(id);
|
||||||
@ -33,9 +35,12 @@ function editorPaste(event, editor) {
|
|||||||
/**
|
/**
|
||||||
* Upload an image file to the server
|
* Upload an image file to the server
|
||||||
* @param {File} file
|
* @param {File} file
|
||||||
|
* @param {WysiwygEditor} wysiwygComponent
|
||||||
*/
|
*/
|
||||||
function uploadImageFile(file) {
|
async function uploadImageFile(file, wysiwygComponent) {
|
||||||
if (file === null || file.type.indexOf('image') !== 0) return Promise.reject(`Not an image file`);
|
if (file === null || file.type.indexOf('image') !== 0) {
|
||||||
|
throw new Error(`Not an image file`);
|
||||||
|
}
|
||||||
|
|
||||||
let ext = 'png';
|
let ext = 'png';
|
||||||
if (file.name) {
|
if (file.name) {
|
||||||
@ -43,11 +48,13 @@ function uploadImageFile(file) {
|
|||||||
if (fileNameMatches.length > 1) ext = fileNameMatches[1];
|
if (fileNameMatches.length > 1) ext = fileNameMatches[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
let remoteFilename = "image-" + Date.now() + "." + ext;
|
const remoteFilename = "image-" + Date.now() + "." + ext;
|
||||||
let formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file, remoteFilename);
|
formData.append('file', file, remoteFilename);
|
||||||
|
formData.append('uploaded_to', wysiwygComponent.pageId);
|
||||||
|
|
||||||
return window.$http.post(window.baseUrl('/images/gallery/upload'), formData).then(resp => (resp.data));
|
const resp = await window.$http.post(window.baseUrl('/images/gallery/upload'), formData);
|
||||||
|
return resp.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerEditorShortcuts(editor) {
|
function registerEditorShortcuts(editor) {
|
||||||
@ -370,7 +377,10 @@ class WysiwygEditor {
|
|||||||
|
|
||||||
constructor(elem) {
|
constructor(elem) {
|
||||||
this.elem = elem;
|
this.elem = elem;
|
||||||
this.textDirection = document.getElementById('page-editor').getAttribute('text-direction');
|
|
||||||
|
const pageEditor = document.getElementById('page-editor');
|
||||||
|
this.pageId = pageEditor.getAttribute('page-id');
|
||||||
|
this.textDirection = pageEditor.getAttribute('text-direction');
|
||||||
|
|
||||||
this.plugins = "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor media";
|
this.plugins = "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor media";
|
||||||
this.loadPlugins();
|
this.loadPlugins();
|
||||||
@ -397,6 +407,9 @@ class WysiwygEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTinyMceConfig() {
|
getTinyMceConfig() {
|
||||||
|
|
||||||
|
const context = this;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selector: '#html-editor',
|
selector: '#html-editor',
|
||||||
content_css: [
|
content_css: [
|
||||||
@ -586,7 +599,7 @@ class WysiwygEditor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Paste image-uploads
|
// Paste image-uploads
|
||||||
editor.on('paste', event => editorPaste(event, editor));
|
editor.on('paste', event => editorPaste(event, editor, context));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import 'codemirror/mode/diff/diff';
|
|||||||
import 'codemirror/mode/go/go';
|
import 'codemirror/mode/go/go';
|
||||||
import 'codemirror/mode/htmlmixed/htmlmixed';
|
import 'codemirror/mode/htmlmixed/htmlmixed';
|
||||||
import 'codemirror/mode/javascript/javascript';
|
import 'codemirror/mode/javascript/javascript';
|
||||||
|
import 'codemirror/mode/lua/lua';
|
||||||
import 'codemirror/mode/markdown/markdown';
|
import 'codemirror/mode/markdown/markdown';
|
||||||
import 'codemirror/mode/nginx/nginx';
|
import 'codemirror/mode/nginx/nginx';
|
||||||
import 'codemirror/mode/php/php';
|
import 'codemirror/mode/php/php';
|
||||||
@ -38,12 +39,13 @@ const modeMap = {
|
|||||||
javascript: 'javascript',
|
javascript: 'javascript',
|
||||||
json: {name: 'javascript', json: true},
|
json: {name: 'javascript', json: true},
|
||||||
js: 'javascript',
|
js: 'javascript',
|
||||||
php: 'php',
|
lua: 'lua',
|
||||||
md: 'markdown',
|
md: 'markdown',
|
||||||
mdown: 'markdown',
|
mdown: 'markdown',
|
||||||
markdown: 'markdown',
|
markdown: 'markdown',
|
||||||
nginx: 'nginx',
|
nginx: 'nginx',
|
||||||
powershell: 'powershell',
|
powershell: 'powershell',
|
||||||
|
php: 'php',
|
||||||
py: 'python',
|
py: 'python',
|
||||||
python: 'python',
|
python: 'python',
|
||||||
ruby: 'ruby',
|
ruby: 'ruby',
|
||||||
|
@ -16,6 +16,7 @@ function mounted() {
|
|||||||
addRemoveLinks: true,
|
addRemoveLinks: true,
|
||||||
dictRemoveFile: trans('components.image_upload_remove'),
|
dictRemoveFile: trans('components.image_upload_remove'),
|
||||||
timeout: Number(window.uploadTimeout) || 60000,
|
timeout: Number(window.uploadTimeout) || 60000,
|
||||||
|
maxFilesize: Number(window.uploadLimit) || 256,
|
||||||
url: function() {
|
url: function() {
|
||||||
return _this.uploadUrl;
|
return _this.uploadUrl;
|
||||||
},
|
},
|
||||||
|
@ -51,15 +51,22 @@
|
|||||||
margin: $-xs $-s $-xs 0;
|
margin: $-xs $-s $-xs 0;
|
||||||
}
|
}
|
||||||
.align-right {
|
.align-right {
|
||||||
float: right !important;
|
text-align: right !important;
|
||||||
}
|
}
|
||||||
img.align-right, table.align-right {
|
img.align-right, table.align-right {
|
||||||
text-align: right;
|
float: right !important;
|
||||||
margin: $-xs 0 $-xs $-s;
|
margin: $-xs 0 $-xs $-s;
|
||||||
}
|
}
|
||||||
.align-center {
|
.align-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
img.align-center {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
img.align-center, table.align-center {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height:auto;
|
height:auto;
|
||||||
|
@ -60,6 +60,39 @@ return [
|
|||||||
'search_created_after' => 'Erstellt nach',
|
'search_created_after' => 'Erstellt nach',
|
||||||
'search_set_date' => 'Datum auswählen',
|
'search_set_date' => 'Datum auswählen',
|
||||||
'search_update' => 'Suche aktualisieren',
|
'search_update' => 'Suche aktualisieren',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shelves
|
||||||
|
*/
|
||||||
|
'shelf' => 'Regal',
|
||||||
|
'shelves' => 'Regale',
|
||||||
|
'shelves_long' => 'Bücherregal',
|
||||||
|
'shelves_empty' => 'Es wurden noch keine Regale angelegt',
|
||||||
|
'shelves_create' => 'Erzeuge ein Regal',
|
||||||
|
'shelves_popular' => 'Beliebte Regale',
|
||||||
|
'shelves_new' => 'Kürzlich erstellte Regale',
|
||||||
|
'shelves_popular_empty' => 'Die beliebtesten Regale werden hier angezeigt.',
|
||||||
|
'shelves_new_empty' => 'Die neusten Regale werden hier angezeigt.',
|
||||||
|
'shelves_save' => 'Regal speichern',
|
||||||
|
'shelves_books' => 'Bücher in diesem Regal',
|
||||||
|
'shelves_add_books' => 'Buch zu diesem Regal hinzufügen',
|
||||||
|
'shelves_drag_books' => 'Bücher hier hin ziehen um sie dem Regal hinzuzufügen',
|
||||||
|
'shelves_empty_contents' => 'Diesem Regal sind keine Bücher zugewiesen',
|
||||||
|
'shelves_edit_and_assign' => 'Regal bearbeiten um Bücher hinzuzufügen',
|
||||||
|
'shelves_edit_named' => 'Bücherregal :name bearbeiten',
|
||||||
|
'shelves_edit' => 'Bücherregal bearbeiten',
|
||||||
|
'shelves_delete' => 'Bücherregal löschen',
|
||||||
|
'shelves_delete_named' => 'Bücherregal :name löschen',
|
||||||
|
'shelves_delete_explain' => "Sie sind im Begriff das Bücherregal mit dem Namen ':name' zu löschen. Enthaltene Bücher werden nicht gelöscht.",
|
||||||
|
'shelves_delete_confirmation' => 'Sind Sie sicher, dass Sie dieses Bücherregal löschen wollen?',
|
||||||
|
'shelves_permissions' => 'Regal-Berechtigungen',
|
||||||
|
'shelves_permissions_updated' => 'Regal-Berechtigungen aktualisiert',
|
||||||
|
'shelves_permissions_active' => 'Regal-Berechtigungen aktiv',
|
||||||
|
'shelves_copy_permissions_to_books' => 'Kopiere die Berechtigungen zum Buch',
|
||||||
|
'shelves_copy_permissions' => 'Berechtigungen kopieren',
|
||||||
|
'shelves_copy_permissions_explain' => 'Hiermit werden die Berechtigungen des aktuellen Regals auf alle enthaltenen Bücher übertragen. Überprüfen Sie vor der Aktivierung, ob alle Berechtigungsänderungen am aktuellen Regal gespeichert wurden.',
|
||||||
|
'shelves_copy_permission_success' => 'Regal-Berechtigungen wurden zu :count Büchern kopiert',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Books
|
* Books
|
||||||
*/
|
*/
|
||||||
|
@ -9,6 +9,13 @@ return [
|
|||||||
'no_pages_recently_created' => 'Du hast bisher keine Seiten angelegt.',
|
'no_pages_recently_created' => 'Du hast bisher keine Seiten angelegt.',
|
||||||
'no_pages_recently_updated' => 'Du hast bisher keine Seiten aktualisiert.',
|
'no_pages_recently_updated' => 'Du hast bisher keine Seiten aktualisiert.',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shelves
|
||||||
|
*/
|
||||||
|
'shelves_delete_explain' => "Du bist im Begriff das Bücherregal mit dem Namen ':name' zu löschen. Enthaltene Bücher werden nicht gelöscht.",
|
||||||
|
'shelves_delete_confirmation' => 'Bist du sicher, dass du dieses Bücherregal löschen willst?',
|
||||||
|
'shelves_copy_permissions_explain' => 'Hiermit werden die Berechtigungen des aktuellen Regals auf alle enthaltenen Bücher übertragen. Überprüfe vor der Aktivierung, ob alle Berechtigungsänderungen am aktuellen Regal gespeichert wurden.',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Books
|
* Books
|
||||||
*/
|
*/
|
||||||
|
@ -27,7 +27,7 @@ return [
|
|||||||
'email' => 'Email',
|
'email' => 'Email',
|
||||||
'password' => 'Wachtwoord',
|
'password' => 'Wachtwoord',
|
||||||
'password_confirm' => 'Wachtwoord Bevestigen',
|
'password_confirm' => 'Wachtwoord Bevestigen',
|
||||||
'password_hint' => 'Minimaal 5 tekens',
|
'password_hint' => 'Minimaal 6 tekens',
|
||||||
'forgot_password' => 'Wachtwoord vergeten?',
|
'forgot_password' => 'Wachtwoord vergeten?',
|
||||||
'remember_me' => 'Mij onthouden',
|
'remember_me' => 'Mij onthouden',
|
||||||
'ldap_email_hint' => 'Geef een email op waarmee je dit account wilt gebruiken.',
|
'ldap_email_hint' => 'Geef een email op waarmee je dit account wilt gebruiken.',
|
||||||
@ -73,4 +73,4 @@ return [
|
|||||||
'email_not_confirmed_click_link' => 'Klik op de link in de e-mail die vlak na je registratie is verstuurd.',
|
'email_not_confirmed_click_link' => 'Klik op de link in de e-mail die vlak na je registratie is verstuurd.',
|
||||||
'email_not_confirmed_resend' => 'Als je deze e-mail niet kunt vinden kun je deze met onderstaande formulier opnieuw verzenden.',
|
'email_not_confirmed_resend' => 'Als je deze e-mail niet kunt vinden kun je deze met onderstaande formulier opnieuw verzenden.',
|
||||||
'email_not_confirmed_resend_button' => 'Bevestigingsmail Opnieuw Verzenden',
|
'email_not_confirmed_resend_button' => 'Bevestigingsmail Opnieuw Verzenden',
|
||||||
];
|
];
|
||||||
|
@ -21,7 +21,9 @@
|
|||||||
<a @click="updateLanguage('Java')">Java</a>
|
<a @click="updateLanguage('Java')">Java</a>
|
||||||
<a @click="updateLanguage('JavaScript')">JavaScript</a>
|
<a @click="updateLanguage('JavaScript')">JavaScript</a>
|
||||||
<a @click="updateLanguage('JSON')">JSON</a>
|
<a @click="updateLanguage('JSON')">JSON</a>
|
||||||
|
<a @click="updateLanguage('Lua')">Lua</a>
|
||||||
<a @click="updateLanguage('PHP')">PHP</a>
|
<a @click="updateLanguage('PHP')">PHP</a>
|
||||||
|
<a @click="updateLanguage('Powershell')">Powershell</a>
|
||||||
<a @click="updateLanguage('MarkDown')">MarkDown</a>
|
<a @click="updateLanguage('MarkDown')">MarkDown</a>
|
||||||
<a @click="updateLanguage('Nginx')">Nginx</a>
|
<a @click="updateLanguage('Nginx')">Nginx</a>
|
||||||
<a @click="updateLanguage('Python')">Python</a>
|
<a @click="updateLanguage('Python')">Python</a>
|
||||||
@ -48,4 +50,4 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,8 +38,9 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="user-language">{{ trans('settings.users_preferred_language') }}</label>
|
<label for="user-language">{{ trans('settings.users_preferred_language') }}</label>
|
||||||
<select name="setting[language]" id="user-language">
|
<select name="setting[language]" id="user-language">
|
||||||
|
|
||||||
@foreach(trans('settings.language_select') as $lang => $label)
|
@foreach(trans('settings.language_select') as $lang => $label)
|
||||||
<option @if(setting()->getUser($user, 'language') === $lang) selected @endif value="{{ $lang }}">{{ $label }}</option>
|
<option @if(setting()->getUser($user, 'language', config('app.default_locale')) === $lang) selected @endif value="{{ $lang }}">{{ $label }}</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user